ReactiveCocoa的bind源碼理解

為了弄清楚"map與flattenMap有什么區(qū)別"這個(gè)問題,對(duì)flattenMap背后的bind方法做一些深入了解。

bind源代碼理解

先看一段使用map的示例代碼:

// <map示例代碼1>
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@(99)];
    [subscriber sendCompleted];
    return nil;
}];

[[signal map:^id(id value) {
    return @([value integerValue] + 1);
}] subscribeNext:^(id x) {
    NSLog(@"%d", [x integerValue]);
}];

這里多考慮一步,可以對(duì)一個(gè)信號(hào)進(jìn)行訂閱讽营,為什么對(duì)信號(hào)的map返回結(jié)果也可以訂閱?

下面將map處理過(guò)程涉及到的源碼貼出來(lái),

// <map源代碼>
- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    RACStream *stream = [[self flattenMap:^(id value) {
        return [class return:block(value)];
    }] setNameWithFormat:@"[%@] -map:", self.name];
    return stream;
}

// <flattenMap源代碼>
- (instancetype)flattenMap:(RACStream * (^)(id value))block {
    Class class = self.class;
    RACStream *stream = [[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];
    
    return stream;
}

block的流轉(zhuǎn)看起來(lái)有些復(fù)雜宦芦,用一張圖來(lái)簡(jiǎn)化:


map方法block流轉(zhuǎn)過(guò)程.png

每個(gè)block的具體說(shuō)明:
1> block是map的參數(shù),這個(gè)block里邊就是對(duì)信號(hào)的值做轉(zhuǎn)換轴脐;
2> block_map是map方法提供給flattenMap的參數(shù)(我們使用block后加map來(lái)標(biāo)志block經(jīng)過(guò)了map)调卑;
3> block_map_flattenMap是flattenMap方法提供給bind的參數(shù)(我們使用block_map后加flattenMap標(biāo)志block_map經(jīng)過(guò)了flattenMap)抡砂;

圖中黃色標(biāo)記分別記錄了每個(gè)對(duì)應(yīng)block的內(nèi)容。其中涉及到的block名字根據(jù)上邊描述做了替換恬涧∽⒁妫可以很方便地看出map->flattenMap->bind這個(gè)流程對(duì)最初的轉(zhuǎn)換信號(hào)值block做了層層包裹。

下面看一下關(guān)鍵的bind代碼:

// <bind源代碼>
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    /*
     * -bind: should:
     *
     * 1. Subscribe to the original signal of values.
     * 2. Any time the original signal sends a value, transform it using the binding block.
     * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
     * 4. If the binding block asks the bind to terminate, complete the _original_ signal.
     * 5. When _all_ signals complete, send completed to the subscriber.
     *
     * If any signal sends an error at any point, send that to the subscriber.
     */
    
    RACSignal *signal = [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACStreamBindBlock bindingBlock = block();
        
        NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
        
        RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
        
        void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
            BOOL removeDisposable = NO;
            
            @synchronized (signals) {
                [signals removeObject:signal];
                
                if (signals.count == 0) {
                    [subscriber sendCompleted];
                    [compoundDisposable dispose];
                } else {
                    removeDisposable = YES;
                }
            }
            
            if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
        };
        
        void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
            @synchronized (signals) {
                [signals addObject:signal];
            }
            
            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];
            
            RACDisposable *disposable = [signal subscribeNext:^(id x) {
                [subscriber sendNext:x];
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(signal, selfDisposable);
                }
            }];
            
            selfDisposable.disposable = disposable;
        };
        
        @autoreleasepool {
            RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
            [compoundDisposable addDisposable:selfDisposable];
            
            RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {          // 對(duì)應(yīng)于說(shuō)明1
                // Manually check disposal to handle synchronous errors.
                if (compoundDisposable.disposed) return;
                
                BOOL stop = NO;
                id signal = bindingBlock(x, &stop);                                   // 對(duì)應(yīng)于說(shuō)明2
                
                @autoreleasepool {
                    if (signal != nil) addSignal(signal);                             // 對(duì)應(yīng)于說(shuō)明3
                    if (signal == nil || stop) {
                        [selfDisposable dispose];
                        completeSignal(self, selfDisposable);
                    }
                }
            } error:^(NSError *error) {
                [compoundDisposable dispose];
                [subscriber sendError:error];
            } completed:^{
                @autoreleasepool {
                    completeSignal(self, selfDisposable);
                }
            }];
            
            selfDisposable.disposable = bindingDisposable;
        }
        
        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -bind:", self.name];
    
    return signal;
}

代碼頭部給的這一段注解講得很清楚:

 /*
     * -bind: should:
     *
     * 1. Subscribe to the original signal of values.   
      《訂閱原始信號(hào)溯捆,也就是self丑搔,也就是示例代碼中接收map消息的signal》
     * 2. Any time the original signal sends a value, transform it using the binding block.
      《當(dāng)原始信號(hào)發(fā)出值時(shí),使用binding block進(jìn)行轉(zhuǎn)換提揍,這個(gè)binding block對(duì)應(yīng)上面源代碼中bindingBlock啤月,對(duì)應(yīng)上圖中block_map_flattenMap那個(gè)block里的return值,
      根據(jù)上圖對(duì)block_map_flattenMap層層解套劳跃,最終是調(diào)用了轉(zhuǎn)換value值的block顽冶。》
     * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
      《如果bindingBlock返回的是signal售碳,使用addSignal這個(gè)block對(duì)返回的signal進(jìn)行訂閱强重。》
     * 4. If the binding block asks the bind to terminate, complete the _original_ signal.
     * 5. When _all_ signals complete, send completed to the subscriber.
     *
     * If any signal sends an error at any point, send that to the subscriber.
     */

1贸人,2间景,3這三點(diǎn)對(duì)照上面代碼,可以用語(yǔ)言描述一下<map示例代碼1>:對(duì)signal發(fā)送map消息艺智,返回一個(gè)signal_rt倘要,這個(gè)signal_rt就是bind方法的返回值;對(duì)signal_rt進(jìn)行訂閱十拣,然后就進(jìn)入了bind注解的1封拧,2,3流程夭问。

flattenMap與map有什么區(qū)別

有了上面的知識(shí)基礎(chǔ)泽西,再來(lái)看flattenMap與map有什么區(qū)別這個(gè)問題。
flattenMap和map的主要區(qū)別在于block_map_flattenMap中的block_map()缰趋,map提供的block_map是這樣的:

^(id value) {
      return [class return:block(value)];    
}

經(jīng)過(guò)查看[class return:block(value)]的內(nèi)部調(diào)用捧杉,其實(shí)[class return:block(value)]可以用 [RACReturnSignal return:block(value)]來(lái)代替。所以map提供的block_map(value)其實(shí)就是一個(gè)RACReturnSignal秘血,map轉(zhuǎn)換后的值被保存在了RACReturnSignal的value屬性中味抖。

而flattenMap提供的block_map()是什么呢?在使用flattenMap時(shí)block_map()是我們需要提供的block參數(shù)灰粮,我們可以返回任意類型的信號(hào)仔涩,不僅僅是RACReturnSignal。

下面看一個(gè)涉及到map與flattenMap使用區(qū)別的一個(gè)例子:ReactiveCocoa入門教程:第一部分

- (RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id subscriber){
   [self.signInService 
     signInWithUsername:self.usernameTextField.text
               password:self.passwordTextField.text
               complete:^(BOOL success){
                    [subscriber sendNext:@(success)];
                    [subscriber sendCompleted];
     }];
   return nil;
}];
}

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

上面使用map并不會(huì)出現(xiàn)登錄結(jié)果粘舟,參考上面的結(jié)論看一下問題出在了哪熔脂,
1> 使用map時(shí)block_map(value)是RACReturnSignal佩研,其對(duì)應(yīng)的value是- (RACSignal *)signInSignal返回的信號(hào),根據(jù)bind源碼說(shuō)明第3條锤悄,會(huì)對(duì)RACReturnSignal進(jìn)行訂閱韧骗,根據(jù)RACReturnSignal使用方法訂閱者最終得到的是- (RACSignal *)signInSignal返回的信號(hào)嘉抒;
2> 使用flattenMap時(shí)零聚,block_map(value)就是- (RACSignal *)signInSignal的返回登錄信號(hào),然后根據(jù)bind源碼說(shuō)明第3條些侍,會(huì)對(duì)這個(gè)登錄信號(hào)進(jìn)行訂閱隶症。

結(jié)論

所以flattenMap和map的區(qū)別在于,flattenMap的block參數(shù)返回一個(gè)“任意類型”信號(hào)RACSignal到bind內(nèi)部去做addSignal(RACSignal)操作來(lái)對(duì)RACSignal進(jìn)行訂閱岗宣;
而map是限定flattenMap只能返回一個(gè)RACReturnSignal信號(hào)去bind內(nèi)部做addSigna(RACReturenSignal)操作來(lái)對(duì)RACReturnSignal進(jìn)行訂閱蚂会,而對(duì)RACReturnSignal進(jìn)行訂閱只能獲取RACReturnSignal內(nèi)部攜帶的value值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耗式,一起剝皮案震驚了整個(gè)濱河市胁住,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刊咳,老刑警劉巖彪见,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異娱挨,居然都是意外死亡余指,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門跷坝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酵镜,“玉大人,你說(shuō)我怎么就攤上這事柴钻』淳拢” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵贴届,是天一觀的道長(zhǎng)缸濒。 經(jīng)常有香客問我,道長(zhǎng)粱腻,這世上最難降的妖魔是什么庇配? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮绍些,結(jié)果婚禮上捞慌,老公的妹妹穿的比我還像新娘。我一直安慰自己柬批,他們只是感情好啸澡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布袖订。 她就那樣靜靜地躺著,像睡著了一般嗅虏。 火紅的嫁衣襯著肌膚如雪洛姑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天皮服,我揣著相機(jī)與錄音楞艾,去河邊找鬼。 笑死龄广,一個(gè)胖子當(dāng)著我的面吹牛硫眯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播择同,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼两入,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了敲才?” 一聲冷哼從身側(cè)響起裹纳,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎紧武,沒想到半個(gè)月后剃氧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脏里,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年她我,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迫横。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡番舆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矾踱,到底是詐尸還是另有隱情恨狈,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布呛讲,位于F島的核電站禾怠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贝搁。R本人自食惡果不足惜吗氏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雷逆。 院中可真熱鬧弦讽,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仿村,卻和暖如春锐朴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔼囊。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工焚志, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人压真。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓娩嚼,卻偏偏與公主長(zhǎng)得像蘑险,于是被迫代替她去往敵國(guó)和親滴肿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容