ReactiveCocoa框架理解二

信號類還提供了一列方法,可以對信號傳遞的數據進行操作。

1.map:方法

map:方法可以將信號傳遞的數據進行映射和轉換,demo如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"]; //傳遞"test"
}];
RACSignal *mappedSignal = [signal map:^id(NSString *value) {
    return @(value.length); //映射成"test"字符串的長度
}];
[mappedSignal subscribeNext:^(id x) {
    NSInteger len = [(NSNumber *)x integerValue];
    NSLog(@"%@",@(len)); //輸出結果,結果是4
} completed:^{
    NSLog(@"completed");
}];

上一篇已經分析了create:方法,創建了一個RACDynamicSignal類型的信號signal,當執行訂閱subscriber時會觸發didSubscribe回調,調用[subscriber sendNext:@"test"]。本例不是由signal直接訂閱subscriber,而是map方法后新的mappedSignal對象訂閱。map:方法如下:

- (RACSignal *)map:(id (^)(id value))block {
    return [super map:block]; //調用父類(RACStrem)的map:方法
}

- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    //調用flattenMap:方法
    return [[self flattenMap:^(id value) {
        return [class return:block(value)];
    }] setNameWithFormat:@"[%@] -map:", self.name];
}

首先調用父類RACStrem的map:方法,接著調用flattenMap:方法,該方法默認由RACStrem類實現,如下:

- (instancetype)flattenMap:(RACStream * (^)(id value))block {
    Class class = self.class;
    return [[self bind:^{
        return ^(id value, BOOL *stop) {
            id stream = block(value) ?: [class empty];
            NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
            return stream;
        };
    }] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

實質是通過bind方法創建一個新的信號。但是由于子類RACSignal重現了flattenMap:方法,故不會調用bind:方法,而是調用如下方法:

- (RACSignal *)flattenMap:(RACSignal * (^)(id value))signalBlock {
    //創建一個RACDynamicSignal,訂閱subscriber時會觸發didSubscribe回調
    return [[RACSignal
        create:^(id<RACSubscriber> subscriber) {
            __block volatile int32_t subscriptions = 0;
            //1.定義一個回調subscribeSignal,傳入signal和nextBlock作為參數
            void (^subscribeSignal)(RACSignal *, void (^)(id)) = ^(RACSignal *signal, void (^nextBlock)(id)) {
                __block RACDisposable *savedDisposable;
                //訂閱信號
                [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
                    savedDisposable = disposable;

                    OSAtomicIncrement32(&subscriptions);
                    [subscriber.disposable addDisposable:savedDisposable];
                } next:nextBlock error:^(NSError *error) {
                    [subscriber sendError:error];
                } completed:^{
                    [subscriber.disposable removeDisposable:savedDisposable];
                    if (OSAtomicDecrement32(&subscriptions) == 0) [subscriber sendCompleted];
                }];
            };
            //2.觸發subscribeSignal回調,傳入信號和nextBlock
            subscribeSignal(self, ^(id x) {
                //創建一個新的signal
                RACSignal *innerSignal = signalBlock(x);

                if (innerSignal == nil) return;
                NSCAssert([innerSignal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", innerSignal);

                //3.再次調用subscribeSignal回調,傳入新的innerSignal和nextBlock
                subscribeSignal(innerSignal, ^(id x) {
                    [subscriber sendNext:x]; //4.初始訂閱者的next事件
                });
            });
        }]
        setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

這是一個比較長的方法,根據注釋,我們將它分成4個部分分析。

1.首先我們將demo中調用flattenMap:方法的信號對象記為signal,調用create:方法返回一個新的RACDynamicSignal,記為mappedSignal。

2.mappedSignal在demo中調用subscribeNext:completed:方法時,創建一個subscriber,記為subscriber0,同時觸發mappedSignal的didSubscribe回調,進入步驟1。

3.首先定義一個subscribeSignal方法,傳入兩個參數,分別是要訂閱的信號和訂閱時執行的nextBlock,然后進行signal的訂閱。

4.調用subscribeSignal方法,傳入signal,觸發signal的didSubscribe邏輯,在demo中是傳遞觸發next事件,傳入"test",如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"]; //傳遞"test"
}];

5.傳回的參數x為"test",然后執行signalBlock(x),signalBlock由上一級map:方法傳入,返回一個RACReturnSignal對象,內部value值是外層轉化后的值,即字符串"test"的長度4,如下:

- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    return [[self flattenMap:^(id value) {
        return [class return:block(value)]; //返回一個新的RACReturnSignal對象,value是block轉化后的值,即為4
    }] setNameWithFormat:@"[%@] -map:", self.name];
}

6.RACReturnSignal對象就是innerSignal,然后再次調用subscribeSignal方法,傳入innerSignal和相應的nextBlock。前一篇文章分析過,對RACReturnSignal類型的對象innerSignal訂閱,attachSubscriber:方法會直接觸發nextBlock,傳遞value值,即4。最后在nextBlock中觸發初始訂閱者subscriber0的next事件,傳遞4,并輸出。

2.filter:方法

filter:方法可以過濾信號的值,來決定后續數據是否繼續傳遞,demo如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
}];
RACSignal *filteredSignal = [signal filter:^BOOL(NSString *value) {
    if (value.length>=4) { //字符串字數大于等于4,信號繼續傳遞
        return YES;
    } else { //字符串字數小于4,信號終止傳遞
        return NO;
    }
}];
[filteredSignal subscribeNext:^(id x) {
    NSString *str = (NSString *)x; 
    NSLog(str); //輸出"test"
} completed:^{
    NSLog(@"completed");
}];

在filter:方法里面通過返回YES或者NO來決定信號數據是否繼續傳遞,demo中根據字符串的個數來判斷。如果返回YES,則輸出字符串,否則不輸出。filter:方法如下:

- (RACSignal *)filter:(BOOL (^)(id value))block {
    return [super filter:block]; //調用基類RACStream的filter:方法
}

- (instancetype)filter:(BOOL (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    //RACSignal的flatternMap:方法
    return [[self flattenMap:^ id (id value) {
        if (block(value)) {
            return [class return:value];
        } else {
            return class.empty;
        }
    }] setNameWithFormat:@"[%@] -filter:", self.name];
}

調用基類RACStream的filter:方法,和map:方法類似,最終也會調用flattenMap:方法,不同的是,filter:方法傳遞的block邏輯不一樣,如果外層block返回YES,則返回一個RACReturnSignal,內部value仍然是原值,如果外層返回NO,則返回一個RACEmptySignal。

回顧一下flattenMap:方法內部處理邏輯,如下:

- (RACSignal *)flattenMap:(RACSignal * (^)(id value))signalBlock {
    return [[RACSignal
        create:^(id<RACSubscriber> subscriber) {
            __block volatile int32_t subscriptions = 0;

            void (^subscribeSignal)(RACSignal *, void (^)(id)) = ^(RACSignal *signal, void (^nextBlock)(id)) {
                __block RACDisposable *savedDisposable;

                [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
                    savedDisposable = disposable;

                    OSAtomicIncrement32(&subscriptions);
                    [subscriber.disposable addDisposable:savedDisposable];
                } next:nextBlock error:^(NSError *error) {
                    [subscriber sendError:error];
                } completed:^{
                    //RACReturnSignal和RACEmptySignal在attachSubscriber都會觸發completed事件
                    [subscriber.disposable removeDisposable:savedDisposable];
                    if (OSAtomicDecrement32(&subscriptions) == 0) {
                        [subscriber sendCompleted];
                    }
                }];
            };

            subscribeSignal(self, ^(id x) {
                RACSignal *innerSignal = signalBlock(x);
                
                if (innerSignal == nil) return;
                NSCAssert([innerSignal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", innerSignal);

                subscribeSignal(innerSignal, ^(id x) {
                    [subscriber sendNext:x];
                });
            });
        }]
        setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

如果innerSignal是RACReturnSignal對象,則再次調用subscribeSignal方法,邏輯和map:方法一樣。如果innerSignal是RACEmptySignal對象,則再次調用subscribeSignal方法,信號訂閱時,會觸發RACEmptySignal的attachSubscriber:方法,如下:

- (void)attachSubscriber:(RACLiveSubscriber *)subscriber {
    NSCParameterAssert(subscriber != nil);
    [subscriber sendCompleted]; //completed事件
}

只會觸發completed事件,不會觸發subscriber的next事件,也就不會觸發初始訂閱者subscriber0的next事件,不會輸出。從而實現filter過濾信號數據的作用。

ignore:方法是在filter:方法的基礎上封裝了一下,實現指定信號數據的忽略。

- (instancetype)ignore:(id)value {
    return [[self filter:^ BOOL (id innerValue) {
        return innerValue != value && ![innerValue isEqual:value];
    }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]];
}
3.concat:方法

concat:方法用于將若干個signal按順序連接,signal按照先后順序依次訂閱subscriber。demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"one"];//發送next事件
    [subscriber sendCompleted];//發送completed事件
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"two"];//發送next事件
    [subscriber sendCompleted];//發送completed事件
}];
[[signal1 concat:signal2] subscribeNext:^(id x) {
    NSString *str = (NSString *)x;
    NSLog(str); //輸出字符串
} completed:^{
    NSLog(@"completed");//輸出"completed"
}];

執行demo,輸出的順序是"one","two","completed"。分析concat:方法,如下:

- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        [self subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            [subscriber sendNext:x]; //觸發next事件
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [signal subscribe:subscriber]; //在completed事件回調中觸發下一個signal的訂閱邏輯
        }];
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}

在demo中,首先signal1被訂閱,sendNext:方法觸發next事件,將數據傳遞給subscriber,輸出"one"。然后sendCompleted:方法,觸發completed事件,引起下一個信號對象signal2的訂閱邏輯。如果signal1不調用sendCompleted方法,則結束流程。subscriber訂閱signal2后,sendNext:方法觸發next事件,輸出字符串"two",sendCompleted方法觸發completed事件,輸出"completed"。

4.merge:方法

merge:方法將多個信號連接起來,實現多個信號依次被訂閱。demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test1"];
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test2"];
}];
RACSignal *mergeSignal = [signal1 merge:signal2];
[mergeSignal subscribeNext:^(id x) {
    NSLog(@"%@",x); //依次輸出"test1"、"test2"
}];

mergerSignal訂閱subscriber時,依次執行signal1和signal2的didSubscribe回調,輸出"test1","test2"。

merge:方法實現如下:

- (RACSignal *)merge:(RACSignal *)signal {
    return [[RACSignal
        merge:@[ self, signal ]]
        setNameWithFormat:@"[%@] -merge: %@", self.name, signal];
}

+ (RACSignal *)merge:(id<NSFastEnumeration>)signals {
    NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];
    for (RACSignal *signal in signals) {
        [copiedSignals addObject:signal];
    }
    return [[copiedSignals.rac_signal flatten] setNameWithFormat:@"+merge: %@", copiedSignals];
}

首先將signal1和signal2存入一個數組中,然后執行NSArray的rac_signal方法,創建一個RACDynamicSignal,訂閱時,依次觸發next事件,并且將signal1和signal2作為參數傳遞。

- (RACSignal *)rac_signal {
    NSArray *collection = [self copy];
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        for (id obj in collection) {
            [subscriber sendNext:obj];
            if (subscriber.disposable.disposed) return;
        }
        [subscriber sendCompleted];
    }] setNameWithFormat:@"%@ -rac_signal", self.rac_description];
}

flatten:方法調用flattenMap:方法,根據上文對flattenMap:方法的分析,首先依次觸發next事件,傳遞signal1和signal2,然后調用signalBlock,返回value。然后調用subscribeSignal()方法,觸發next事件,依次輸出test1和test2。

- (instancetype)flatten {
    __weak RACStream *stream __attribute__((unused)) = self;
    return [[self flattenMap:^(id value) {
        return value;
    }] setNameWithFormat:@"[%@] -flatten", self.name];
}
5.zipWith:方法

zipWith:方法的作用是將兩個信號發出的數據壓縮成一個傳遞,即subscriber調用一次sendNext,demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test1"];
    [subscriber sendCompleted];
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test2"];
    [subscriber sendCompleted];
}];
RACSignal *zipSignal = [signal1 zipWith:signal2];
[zipSignal subscribeNext:^(id x) {
    RACTuple *tuple = (RACTuple *)x;
    for (NSString *value in tuple.array) {
        NSLog(@"%@",value);
    }
} completed:^{
    NSLog(@"completed");
}];

signal1和signal2被zipWith:方法壓縮成一個信號zipSignal,輸出的內容是一個RACTuple對象,包含signal1和signal2發送的數據"test1"和"test2",依次輸出。下面分析一下zipWith:方法:

- (RACSignal *)zipWith:(RACSignal *)signal {
    NSCParameterAssert(signal != nil);
    //創建一個RACDynamic信號
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        //管理signal1的數據集合
        __block BOOL selfCompleted = NO;
        NSMutableArray *selfValues = [NSMutableArray array];
        //管理signal2的數據集合
        __block BOOL otherCompleted = NO;
        NSMutableArray *otherValues = [NSMutableArray array];
        //是否觸發subscriber的completed事件
        void (^sendCompletedIfNecessary)(void) = ^{
            @synchronized (selfValues) {
                BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
                BOOL otherEmpty = (otherCompleted && otherValues.count == 0);
                if (selfEmpty || otherEmpty) [subscriber sendCompleted];
            }
        };
        //是否觸發subscriber的next事件
        void (^sendNext)(void) = ^{
            @synchronized (selfValues) {
                if (selfValues.count == 0) return;
                if (otherValues.count == 0) return;

                RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil];
                [selfValues removeObjectAtIndex:0];
                [otherValues removeObjectAtIndex:0];

                [subscriber sendNext:tuple];
                sendCompletedIfNecessary();
            }
        };
        //signal1被訂閱
        [self subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            @synchronized (selfValues) {
                [selfValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                selfCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
        //signal2被訂閱
        [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            @synchronized (selfValues) {
                [otherValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                otherCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
    }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}

該方法包含如下步驟:

1、維護selfValues和otherValues兩個數組,里面分別包含signal1和signal2發出的信號數據,以及selfCompleted和otherCompleted兩個變量,表示信號響應是否完成。

2、signal1訂閱,觸發next事件,將"test1"加入selfValues,觸發sendNext()方法,由于此時otherValues尚沒有數據,直接return。signal1的觸發subscriber的completed事件,selfCompleted變為YES。

3、signal2訂閱,觸發next事件,將"test2"加入otherValues,觸發sendNext()方法,將selfValues和otherValues數組中的第一個元素去除,拼成一個RACTuple對象,作為信號數據傳遞給subscriber的next事件,輸出tuple中的各個元素。

4、signal2觸發completed事件,otherCompleted變為YES。調用sendCompletedIfNecessary(),由于selfCompleted或者otherCompleted變量置為Yes,且selfValues和otherValues數組中元素為空,觸發subscriber的completed事件,輸出"completed"。

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

推薦閱讀更多精彩內容