ReactiveCocoa 学习笔记

先贴上我看的博客,大部分内容来自以下博客

  1. 入门教程

  2. sunnyxx的博客,共四篇

  3. 美团的四篇博客

  4. 一篇关于replay的详细讲解与对比

  5. 四篇李忠的博客

  6. 老外的教程:coursera 上有一门课是讲 Reactive Programming

  7. 一篇ReactiveCocoa v2.5 源码解析之架构总览

  8. 另外要关于 Monad 的也有一篇 《Functor、Applicative 和 Monad》

  9. 唐巧大神写的关于Monad 的博客,烧脑系列,最好是先看第一个Optional看

学的时候最好先建个工程,敲个Demo,还可以及时跳进去研究一下RAC的代码,思路会清晰很多

什么是Functional Reactive Programming

Functional Reactive Programming(以下简称FRP)是一种响应变化的编程范式。先来看一小段代码

a = 2
b = 2
c = a + b // c is 4

b = 3

now what is the value of c?
如果使用FRP,c 的值将会随着 b 的值改变而改变,所以叫做「响应式编程」。比较直观的例子就是Excel,当改变某一个单元格的内容时,该单元格相关的计算结果也会随之改变。

image
image

FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合,就不需要去监听某个值或事件。

先了解链式,函数式,响应式编程

1.链式编程怎么写(还达不到谈什么编程思想)

核心是返回本身或者一个新的对象,传入的一般是一个block值,这样可以进行一些额外的操作。
而如果你想调用点语法,在oc中就声明一个函数类型的值(一等值)block,也就是closure类型的值。这个值需要传进一个待操作的值,返回一个新的对象。

//1.这个方法没办法连起来
[caculator add:10];
//2.结果存在这个对象里面,并且方法返回本身,就可以连加
int num1 = [[[caculator addd:10] addd:10] addd:20].result;

/*
 3.再进一步改造,通过属性的getter方法来使用点语法,但既能当属性,又有传参和返回值的就只有block了。
   先复习一下block,记得^就是 oc 中block的类,后面可跟block名称,相同形参类型,相同返回值类型的block 就是同一种block
 */

UIButton * (^setButton)(CGFloat ,CGFloat,UIColor *,NSString *) = ^(CGFloat cornerRadius,CGFloat borderWidth, UIColor * backGroundColor,NSString *title){
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(0, 0, 100, 100);
    button.layer.cornerRadius = cornerRadius;
    button.layer.masksToBounds = YES;
    button.layer.borderWidth = borderWidth;
    button.backgroundColor = backGroundColor;
    [button setTitle:title forState:UIControlStateNormal];
    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    return button;
    
};


NSArray * arr;

//UIButton *button = setButton();
//UIButton *button = setButton(1.0,2.0,[UIColor redColor],@"blcokButton");

caculator.add(1);
//到此结束
int num2 = caculator.initial(4).add(1).add(2).add(3).result;

int num3 = caculator.initial(6).add(1).add(2).divide(1).sub(2).result;

NSLog(@"num2:----%d num3:----%d",num2,num3);
//总结:
- (CaculatorManager *)addd:(int)num
{
    self.result += num;
    return self;
}

//.h声明
#import <Foundation/Foundation.h>

@class CaculatorManager;

typedef CaculatorManager *(^CaculatorBlock)(int);

@interface CaculatorManager : NSObject

@property (nonatomic, assign) int           result;
@property (nonatomic, copy) CaculatorBlock  add;
@property (nonatomic, copy) CaculatorBlock  sub;
@property (nonatomic, copy) CaculatorBlock  muilt;

- (int)add:(int)num;


- (CaculatorManager *)addd:(int)num;

//- (CaculatorManager *(^)(int))add;

- (CaculatorBlock)divide;

- (CaculatorBlock)initial;

//.m 实现
- (CaculatorBlock)add
{
    if (!_add) {
        __weak typeof(self) _self = self;
        _add = ^(int newValue){
        
            _self.result += newValue;
            return _self;
        };
    }
    return _add;
}   

- (CaculatorBlock)divide
{
    return ^ CaculatorManager *(int newValue){
        self.result /= newValue;
        return self;
    };
}

- (CaculatorBlock)initial
{
    return ^(int newValue){
        self.result = newValue;
        return self;
    };
}

响应式编程

可以参考KVO,当一个属性值改变的时候,就会通知他的订阅者,订阅者可以拿到最新的值。多次改变产生多个值,就像信号流不断流向水盆(订阅者)里面。

函数式编程怎么写

函数式编程可以说是面向值的编程,就是再做数学应用题,自己根究场景,总结规律,抽象出类似映射关系的函数表达式,给一个确定的值,就会输出固定的值。

函数在 Swift 中是一等值 ( rst-class-values),和int、string一样,换句话说,函数可以
作为参数被传递到其它函 数,也可以作为其它函数的返回值。

在oc 中也有一等函数,如block。但是oc中block要比swift麻烦一些。

在函数编程思想中,给函数值(closure)命名的时候,尽量以命名name:String的思维去命名closure,因为它也是值,跟结构体,整型或者bool一样。例如:let Region:(position:CGPoint)->Bool 而不是isInRegion:(position:CGPoint)->Bool

在函数编程中,尽量将负责的程序分解成小的单元模块,而所有这些块可以通过函数装配起来, 以定义一个完整的程序。当然,只有当我们能够避免在两个独立组件之间共享状态时, 才能将一个大型程序分解为更小的单元。

一般函数会返回对象本身,将block当做参数,这个block中的形参是要操作的值,block的返回值就是操作结果。这个对象一般会拥有和操作结果相同类型的字段,保留这个操作结果,还会有block这样的一等值字段,保留这个block,保证block的调用时机。

reactiveCocoa有 fileter map combineLatest 等操作

可以这样理解,有一个水龙头(signal),里面没有水,里面是有一个或多个玻璃球(value组成的信号流),和水龙头半径差不多的玻璃球,这样就不会出现并排的情况(数据是线性的,没有并发)。水龙头默认是关的,除非有接收方(subsciber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。你也可以在水龙头上加个滤嘴,不符合的不让通过。也可以加一个改动装置,八球改变成符合自己的需求(map)。也可以吧多个水龙头合并成一个新的水龙头,这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。 --- 沃 ·镃基朔德

这里有两片非常不错的博客 来指导大家学习使用RAC

RAC入门教程第一部分

RAC入门教程第二部分

image

就以上两篇文章总结一下知识点,简单说下RAC的基本使用

1.创建信号:signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {//订阅时执行
[subscriber sendError:accessError]; //出现错误
[subscriber sendNext:obj]; //通知订阅者来操作obj
[subscriber sendCompleted]; //完成发送,移除订阅者
}]

通过一些UI类别可以快速得到一个信号以供订阅
如 TextField.rac_textSignal
Button.rac_signalForControlEvents:UIControlEventTouchUpInside
在对应的空间后面输入rac_就会有提示。还可以这样RAC(self.titleLabel,text,@“没有的话就是这个默认值”) = TextField.rac_textSignal

2.订阅信号:subscibe: [siganal subscribeNext:^(id x){处理最近的next事件}error:{处理最先发生的error事件}]

3.筛选事件:

- filter: 把信号进行筛选,满足筛选条件的才会传给订阅者,如果用了combine,会保留最新满足的值。衍生函数有self.inputTextField.rac_textSignal ignore:@"sunny",还有 ignoreValues 这个比较极端,忽略所有值,只关心Signal结束,也就是只取Comletion和Error两个消息,中间所有值都丢弃.

- distinctUntilChanged :不是filter 的衍生, 它将这一次的值与上一次做比较,当相同时(也包括- isEqual:)被忽略掉 [RACObserve(self.user, username) distinctUntilChanged]

还有 take(取) skip(跳)
- take: (NSUInteger)
从开始一共取N次的next值,不包括Competion和Error
- takeLast: (NSUInteger)
取最后N次的next值,注意,由于一开始不能知道这个Signal将有多少个next值,所以RAC实现它的方法是将所有next值都存起来,然后原Signal完成时再将后N个依次发送给接收者,但Error发生时依然是立刻发送的。
- takeUntil:(RACSignal *) 当给定的signal sendNext时,当前的signal就sendCompleted,把这个管道封住。例如cell的重用,cell上面有个btn,每次重用的时候,要把之前的btn相关的信号的水龙头去掉,再重新订阅。cell.detailButton rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal
- takeUntilBlock:(BOOL (^)(id x))
对于每个next值,运行block,当block返回YES时停止取值
[self.inputTextField.rac_textSignal takeUntilBlock:^BOOL(NSString *value) {
return [value isEqualToString:@"stop"]
- takeWhileBlock:(BOOL (^)(id x))
上面的反向逻辑,对于每个next值,block返回 YES时才取值
- skip:(NSUInteger)
从开始跳过N次的next值
- skipUntilBlock:(BOOL (^)(id x))
和- takeUntilBlock:同理,一直跳,直到block为YES

- skipWhileBlock:(BOOL (^)(id x))

和- takeWhileBlock:同理,一直跳,直到block为NO

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

4.映射转换:map: 将信号传过来的值转换成自己想要的值,然后再传给订阅者。(函数式编程就是面对值的编程)

    [[[self.usernameTextField.rac_textSignal
      map:^id(NSString*text){
        return @(text.length);
      }]
      filter:^BOOL(NSNumber*length){
        return[length integerValue] > 3;
      }]
      subscribeNext:^(id x){
        NSLog(@"%@", x);
      }];

5.多信号绑定 combine:

    RACSignal *signUpActiveSignal =
    [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
                    reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){
                      return @([usernameValid boolValue]&&[passwordValid                        boolValue]);
                    }];

                        
combineLatest:reduce:方法把signal1和signal2产生的最新的值聚合在一起,当两个信号都有值的时候会合并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,**如果中间其中一个有fillter 且没有满足条件,那么会保留最近满足条件的那个值**,block的返回值会发给下一个信号。
                    
** 这里解释一下signal 的分割和聚合 **

** 1. 分割——信号可以有很多subscriber,也就是作为很多后续步骤的源。注意上图中那个用来表示用户名和密码有效性的布尔信号,它被分割成多个,用于不同的地方。**

** 2. 聚合——多个信号可以聚合成一个新的信号,在上面的例子中,两个布尔信号聚合成了一个。实际上你可以聚合并产生任何类型的信号。 **      

6.取双层信号中内层信号的值:flattenMap: 将异步请求包装在signal里面 再通过flattenMap 拿到信号中的信号的值作为往下传递的值。
flattenMap方法通过调用block(value)来创建一个新的方法,它可以灵活的定义新创建的信号。
map方法,将会创建一个和原来一模一样的信号,只不过新的信号传递的值变为了block(value)。

** map创建一个新的信号,信号的value是block(value),也就是说,如果block(value)是一个信号,那么就是信号的value仍然是信号。如果是flattenMap则会继续调用这个信号的value,作为新的信号的value。 **



    [[[self.signInButton
       rac_signalForControlEvents:UIControlEventTouchUpInside]
       flattenMap:^id(id x){
         return[self signInSignal];
       }]
       subscribeNext:^(id x){
         NSLog(@"Sign in result: %@", x);
       }];

7.添加附加操作 doNext: (side - effects)函数式编程是不建议在函数操作的过程中,改变事件本身。如我们在点击按钮后,让其在请求完成之前置为不可用状态。

    [[[[self.signInButton
       rac_signalForControlEvents:UIControlEventTouchUpInside]
       doNext:^(id x){
         self.signInButton.enabled =NO;
         self.signInFailureText.hidden =YES;
       }]
       flattenMap:^id(id x){
         return[self signInSignal];
       }]
       subscribeNext:^(NSNumber*signedIn){
         self.signInButton.enabled =YES;
         BOOL success =[signedIn boolValue];
         self.signInFailureText.hidden = success;
         if(success){
           [self performSegueWithIdentifier:@"signInSuccess" sender:self];
         }
       }];

8.RAC宏允许直接把信号的输出的值应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。
9.then 会等待上一个signal中completed事件的发送,然后再订阅then block 返回的block.这样就高效的把控制权从一个signal 传给了下一个。当然error事件会跳过then方法,直接执行subscribeNext:error的中error事件

10.deliverOn RACScheduler 线程 对GCD的简单封装

11.throttle:1 节流,拿到传过来的signal后产生一个新的信号,在间隔1s的时间内如果有新的signal流进来,那么就保留最新的,如果1s内没有新的signal了,就发送next事件,继续往下走。适合在类似地点搜索,快速的网络搜索

上面任何的信号转换都是拿到原有信号再产生新的信号,注意下新信号对原有信号订阅的时机是在新信号被订阅的时候才会去订阅源信号。新信号生成同样调用createSignal方法,在didsubscribe回调中对原有信号进行订阅,当最后生成的新的信号被订阅的时候(subscribe:)会调用自己的didsubscribe,然后订阅原有信号,执行原有信号的didsubscribe:

可以这样理解:一个水管接另一个水管,但都是结冰的死的冷信号,只有最后一个被subscribe了,即装上了水龙头,才算有了出口,这样就打通了整个管道,变成流动的活的热信号。

12.关于冷信号的副作用,以及冷信号与热信号之间的转换signal - subject

    热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。
    热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。

先看下冷信号的一般实现步骤

步骤一:[RACSignal createSignal]来获得signal

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:" ];
}

[RACSignal createSignal]会调用子类RACDynamicSignal的createSignal来返回一个signal,并在signal中保存后面的 didSubscribe这个block

步骤二:订阅信号 [signal subscribeNext:]来获得subscriber,然后进行subscription

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;
}

[signal subscribeNext]先会获得一个subscriber,这个subscriber中保存了nextBlock、errorBlock、completedBlock
由于这个signal其实是RACDynamicSignal类型的,这个[self subscribe]方法会调用步骤一中保存的didSubscribe,参数就是1中的subscriber
步骤三:进入didSubscribe,通过[subscriber sendNext:]来执行next block

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

举个栗子:

    self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]];

    self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
    self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];

    @weakify(self)
    RACSignal *fetchData = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        @strongify(self)
        NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id            responseObject) {
            [subscriber sendNext:responseObject];
            [subscriber sendCompleted];
        } failure:^(NSURLSessionDataTask *task, NSError *error) {
            [subscriber sendError:error];
        }];
        return [RACDisposable disposableWithBlock:^{
            if (task.state != NSURLSessionTaskStateCompleted) {
                [task cancel];
            }
        }];
    }];

    RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
        if ([value[@"title"] isKindOfClass:[NSString class]]) {
            return [RACSignal return:value[@"title"]];
        } else {
            return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
        }
    }];

    RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
        if ([value[@"desc"] isKindOfClass:[NSString class]]) {
            return [RACSignal return:value[@"desc"]];
        } else {
            return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
        }
    }];

    RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) {
        NSError *error = nil;
        RenderManager *renderManager = [[RenderManager alloc] init];
        NSAttributedString *rendered = [renderManager renderText:value error:&error];
        if (error) {
            return [RACSignal error:error];
        } else {
            return [RACSignal return:rendered];
        }
    }];

    RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]]  startWith:@"Loading..."];
    RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
    RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];

    [[RACSignal merge:@[title, desc, renderedDesc]] subscribeError:^(NSError *error) {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:error.domain delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alertView show];
    }];

从上面可以看到 fatchData 被间接订阅了6次,那么它的didSubscribe也会走6次,也就是请求6次。这就是冷信号的副作用,每次订阅都重新计算,而在函数编程中 纯函数的调用在相同参数下的返回值第二次不需要计算。在oc中怎么做到,是不是可以做一些缓冲,或保存一下计算结果,自己控制需不需要重新计算。

RAC规避副作用的做法就是将冷信号转成热信号。冷信号与热信号的本质区别在于是否保持状态,冷信号的多次订阅是不保持状态的,而热信号的多次订阅可以保持状态。所以一种将冷信号转换为热信号的方法就是,将冷信号订阅,订阅到的每一个时间通过RACSbuject发送出去,其他订阅者只订阅这个RACSubject。

以下这些就是冷信号到热信号的转变

RACSignal+Operation.h中

这5个方法中,最为重要的就是- (RACMulticastConnection *)multicast:(RACSubject *)subject;这个方法了,其他几个方法也是间接调用它的。

- (RACMulticastConnection *)publish;

- (RACMulticastConnection *)multicast:(RACSubject *)subject;

- (RACSignal *)replay;

- (RACSignal *)replayLast;

- (RACSignal *)replayLazily;

RACMulticastConnection.m中:

/// implementation RACSignal (Operations)
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
    [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
    return connection;
}

- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source != nil);
    NSCParameterAssert(subject != nil);
    self = [super init];
    if (self == nil) return nil;
    _sourceSignal = source;
    _serialDisposable = [[RACSerialDisposable alloc] init];
    _signal = subject;

    return self;
}

- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
    if (shouldConnect) {
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; //调用RACSignal的subcribe:真正的订阅。
        
    }
    return self.serialDisposable;
}

- (RACSignal *)autoconnect {
    __block volatile int32_t subscriberCount = 0;

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            OSAtomicIncrement32Barrier(&subscriberCount);

            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
            RACDisposable *connectionDisposable = [self connect];

            return [RACDisposable disposableWithBlock:^{
                [subscriptionDisposable dispose];

                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
                    [connectionDisposable dispose];
                }
            }];
        }]
        setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
  • 当RACSignal类的实例调用- (RACMulticastConnection *)multicast:(RACSubject *)subject时,以self和subject作为构造参数创建一个RACMulticastConnection实例。
  • RACMulticastConnection构造的时候,保存source和subject作为成员变量,创建一个RACSerialDisposable对象,用于取消订阅。
  • 当RACMulticastConnection类的实例调用- (RACDisposable *)connect这个方法的时候,判断是否是第一次。如果是的话用_signal这个成员变量来订阅sourceSignal之后返回self.serialDisposable;否则直- 接返回self.serialDisposable。这里面订阅sourceSignal是重点。
  • RACMulticastConnection的signal只读属性,就是一个热信号,订阅这个热信号就避免了各种副作用的问题。它会在- (RACDisposable *)connect第一次调用后,根据sourceSignal的订阅结果来传递事件。
  • 想要确保第一次订阅就能成功订阅sourceSignal,可以使用- (RACSignal *)autoconnect这个方法,它保证了第一个订阅者触发sourceSignal的订阅,也保证了当返回的信号所有订阅者都关闭连接后sourceSignal被正确关闭连接。

下面再来看看其他几个方法的实现:

/// implementation RACSignal (Operations)
- (RACMulticastConnection *)publish {
    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
    RACMulticastConnection *connection = [self multicast:subject];
    return connection;
}

- (RACSignal *)replay {
    RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}

- (RACSignal *)replayLast {
    RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}

- (RACSignal *)replayLazily {
    RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
    return [[RACSignal
        defer:^{
            [connection connect];
            return connection.signal;
        }]
        setNameWithFormat:@"[%@] -replayLazily", self.name];
}

这几个方法的实现都相当简单,只是为了简化而封装,具体说明一下:

  • (RACMulticastConnection *)publish就是帮忙创建了RACSubject。

  • (RACSignal *)replay就是用RACReplaySubject来作为subject,并立即执行connect操作,返回connection.signal。其作用是上面提到的replay功能,即后来的订阅者可以收到历史值。

  • (RACSignal *)replayLast就是用Capacity为1的RACReplaySubject来替换- (RACSignal *)replay的`subject。其作用是使后来订阅者只收到最后的历史值。

  • (RACSignal *)replayLazily和- (RACSignal *)replay的区别就是replayLazily会在第一次订阅的时候才订阅sourceSignal。

  • 到这里,就清楚了,咱们只需要把之前请求的fatchData = [fatchData replayLazily] 就好了。

看下subject 的实现细节

- (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];
        }
    }];
}

从subscribe:的实现可以看出,对RACSubject对象的每次subscription,都是将这个subscriber加到subscribers数组中,并没有调用 didSubScirbe()

对比下RACDynamicSignal的subscibe:

- (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;
}

其中会调用 self.didSubscribe(subscriber),调用了 这个也就会调用其中的sendnext.

- (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;
}

subject的 sendNext:

- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

RACReplaySubject

RACReplaySubject.m中:

- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    if (self == nil) return nil;

    _capacity = capacity;
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);

    return self;
}

从init中我们看出,RACReplaySubject对象持有capacity变量(用于决定valuesReceived缓存多少个sendNext:出来的value,这在区分replay和replayLast的时候特别有用)以及valuesReceived数组(用来保存sendNext:出来的value),这二者接下来会重点涉及到

- (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 == RACTupleNil.tupleNil ? 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;

从subscribe:可以看出,RACReplaySubject对象每次subscription,都会把之前valuesReceived中buffer的value重新sendNext一遍,然后调用super把当前的subscriber加入到subscribers数组中

- (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)];
        }
    }
}

从sendNext:可以看出,RACReplaySubject对象会buffer每次sendNext的value,然后会调用super,对subscribers中的每个subscriber,调用sendNext。buffer的数量是根据self.capacity来决定的。

冷热信号的内容全部来自美团的技术博客

未完待续。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容