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閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容