ReactiveCocoa 中 RACSignal 所有變換操作底層實(shí)現(xiàn)分析(下)

前言

緊接著上篇的源碼實(shí)現(xiàn)分析道偷,繼續(xù)分析RACSignal的變換操作的底層實(shí)現(xiàn)匾鸥。

目錄

  • 1.高階信號(hào)操作
  • 2.同步操作
  • 3.副作用操作
  • 4.多線程操作
  • 5.其他操作

一. 高階信號(hào)操作

高階操作大部分的操作是針對(duì)高階信號(hào)的桅滋,也就是說信號(hào)里面發(fā)送的值還是一個(gè)信號(hào)或者是一個(gè)高階信號(hào)∩桶耄可以類比數(shù)組砌左,這里就是多維數(shù)組,數(shù)組里面還是套的數(shù)組悦施。

1. flattenMap: (在父類RACStream中定義的)

flattenMap:在整個(gè)RAC中具有很重要的地位并扇,很多信號(hào)變換都是可以用flattenMap:來實(shí)現(xiàn)的。

map:抡诞,flatten穷蛹,filter,sequenceMany:這4個(gè)操作都是用flattenMap:來實(shí)現(xiàn)的昼汗。然而其他變換操作實(shí)現(xiàn)里面用到map:肴熏,flatten,filter又有很多顷窒。

回顧一下map:的實(shí)現(xiàn):


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

map:的操作其實(shí)就是直接原信號(hào)進(jìn)行的 flattenMap:的操作蛙吏,變換出來的新的信號(hào)的值是block(value)源哩。

flatten的實(shí)現(xiàn)接下去會(huì)具體分析,這里先略過鸦做。

filter的實(shí)現(xiàn):


- (instancetype)filter:(BOOL (^)(id value))block {
    NSCParameterAssert(block != nil);
    
    Class class = self.class;
    return [[self flattenMap:^ id (id value) {
        block(value) ? return [class return:value] :  return class.empty;
    }] setNameWithFormat:@"[%@] -filter:", self.name];
}

filter的實(shí)現(xiàn)和map:有點(diǎn)類似励烦,也是對(duì)原信號(hào)進(jìn)行 flattenMap:的操作,只不過block(value)不是作為返回值泼诱,而是作為判斷條件坛掠,滿足這個(gè)閉包的條件,變換出來的新的信號(hào)返回值就是value治筒,不滿足的就返回empty信號(hào)

接下去要分析的高階操作里面屉栓,switchToLatest,try:耸袜,tryMap:的實(shí)現(xiàn)中也將會(huì)使用到flattenMap:友多。

flattenMap:的源碼實(shí)現(xiàn):


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


flattenMap:的實(shí)現(xiàn)是調(diào)用了bind函數(shù),對(duì)原信號(hào)進(jìn)行變換堤框,并返回block(value)的新信號(hào)域滥。關(guān)于bind操作的具體流程這篇文章里面已經(jīng)分析過了,這里不再贅述蜈抓。

從flattenMap:的源碼可以看到骗绕,它是可以支持類似Promise的串行異步操作的,并且flattenMap:是滿足Monad中bind部分定義的资昧。flattenMap:沒法去實(shí)現(xiàn)takeUntil:和take:的操作。

然而荆忍,bind操作可以實(shí)現(xiàn)take:的操作格带,bind是完全滿足Monad中bind部分定義的。

2. flatten (在父類RACStream中定義的)

flatten的源碼實(shí)現(xiàn):


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


flatten操作必須是對(duì)高階信號(hào)進(jìn)行操作刹枉,如果信號(hào)里面不是信號(hào)叽唱,即不是高階信號(hào),那么就會(huì)崩潰微宝。崩潰信息如下:


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Value returned from -flattenMap: is not a stream

所以flatten是對(duì)高階信號(hào)進(jìn)行的降階操作棺亭。高階信號(hào)每發(fā)送一次信號(hào),經(jīng)過flatten變換蟋软,由于flattenMap:操作之后镶摘,返回的新的信號(hào)的每個(gè)值就是原信號(hào)中每個(gè)信號(hào)的值。

如果對(duì)信號(hào)A岳守,信號(hào)B凄敢,信號(hào)C進(jìn)行merge:操作,可以達(dá)到和flatten一樣的效果湿痢。


    [RACSignal merge:@[signalA,signalB,signalC]];

merge:操作在上篇文章分析過涝缝,再來復(fù)習(xí)一下:


+ (RACSignal *)merge:(id<NSFastEnumeration>)signals {
    NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];
    for (RACSignal *signal in signals) {
        [copiedSignals addObject:signal];
    }
    
    return [[[RACSignal
              createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
                  for (RACSignal *signal in copiedSignals) {
                      [subscriber sendNext:signal];
                  }
                  
                  [subscriber sendCompleted];
                  return nil;
              }]
             flatten]
            setNameWithFormat:@"+merge: %@", copiedSignals];
}

現(xiàn)在在回來看這段代碼,copiedSignals雖然是一個(gè)NSMutableArray,但是它近似合成了一個(gè)上圖中的高階信號(hào)拒逮。然后這些信號(hào)們每發(fā)送出來一個(gè)信號(hào)就發(fā)給訂閱者罐氨。整個(gè)操作如flatten的字面意思一樣,壓平滩援。

另外栅隐,在ReactiveCocoa v2.5中,flatten默認(rèn)就是flattenMap:這一種操作狠怨。


public func flatten(_ strategy: FlattenStrategy) -> Signal<Value.Value, Error> {
    switch strategy {
    case .merge:
        return self.merge()
        
    case .concat:
        return self.concat()
        
    case .latest:
        return self.switchToLatest()
    }
}

而在ReactiveCocoa v3.x约啊,v4.x,v5.x中佣赖,flatten的操作是可以選擇3種操作選擇的恰矩。merge,concat憎蛤,switchToLatest外傅。

3. flatten:

flatten:操作也必須是對(duì)高階信號(hào)進(jìn)行操作,如果信號(hào)里面不是信號(hào)俩檬,即不是高階信號(hào)萎胰,那么就會(huì)崩潰。

flatten:的實(shí)現(xiàn)比較復(fù)雜棚辽,一步步的來分析:


- (RACSignal *)flatten:(NSUInteger)maxConcurrent {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init];
        NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent];
        NSMutableArray *queuedSignals = [NSMutableArray array];

        __block BOOL selfCompleted = NO;
        __block void (^subscribeToSignal)(RACSignal *);
        __weak __block void (^recur)(RACSignal *);
        recur = subscribeToSignal = ^(RACSignal *signal) { // 暫時(shí)省略};

        void (^completeIfAllowed)(void) = ^{ // 暫時(shí)省略};
        
        [compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) {
            if (signal == nil) return;
            
            NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);
            
            @synchronized (subscriber) {
                if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) {
                    [queuedSignals addObject:signal];
                    return;
                }
            }
            
            subscribeToSignal(signal);
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (subscriber) {
                selfCompleted = YES;
                completeIfAllowed();
            }
        }]];
        
        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent];
}


先來解釋一些變量技竟,數(shù)組的作用

activeDisposables里面裝的是當(dāng)前正在訂閱的訂閱者們的disposables信號(hào)。

queuedSignals里面裝的是被暫時(shí)緩存起來的信號(hào)屈藐,它們等待被訂閱榔组。

selfCompleted表示高階信號(hào)是否Completed。

subscribeToSignal閉包的作用是訂閱所給的信號(hào)联逻。這個(gè)閉包的入?yún)?shù)就是一個(gè)信號(hào)搓扯,在閉包內(nèi)部訂閱這個(gè)信號(hào),并進(jìn)行一些操作包归。

recur是對(duì)subscribeToSignal閉包的一個(gè)弱引用锨推,防止strong-weak循環(huán)引用,在下面會(huì)分析subscribeToSignal閉包公壤,就會(huì)明白為什么recur要用weak修飾了换可。

completeIfAllowed的作用是在所有信號(hào)都發(fā)送完畢的時(shí)候,通知訂閱者厦幅,給訂閱者發(fā)送completed锦担。

入?yún)axConcurrent的意思是最大可容納同時(shí)被訂閱的信號(hào)個(gè)數(shù)。

再來詳細(xì)分析一下具體訂閱的過程慨削。

flatten:的內(nèi)部洞渔,訂閱高階信號(hào)發(fā)出來的信號(hào)套媚,這部分的代碼比較簡單:



    [self subscribeNext:^(RACSignal *signal) {
        if (signal == nil) return;
    
        NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);
    
        @synchronized (subscriber) {
            // 1
            if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) {
                [queuedSignals addObject:signal];
                return;
            }
        }
        // 2
        subscribeToSignal(signal);
    } error:^(NSError *error) {
        [subscriber sendError:error];
    } completed:^{
        @synchronized (subscriber) {
            selfCompleted = YES;
            // 3
            completeIfAllowed();
        }
    }]];

  1. 如果當(dāng)前最大可容納信號(hào)的個(gè)數(shù) > 0 ,且磁椒,activeDisposables數(shù)組里面已經(jīng)裝滿到最大可容納信號(hào)的個(gè)數(shù)堤瘤,不能再裝新的信號(hào)了。那么就把當(dāng)前的信號(hào)緩存到queuedSignals數(shù)組中浆熔。

  2. 直到activeDisposables數(shù)組里面有空的位子可以加入新的信號(hào)本辐,那么就調(diào)用subscribeToSignal( )閉包,開始訂閱這個(gè)新的信號(hào)医增。

  3. 最后完成的時(shí)候標(biāo)記變量selfCompleted為YES慎皱,并且調(diào)用completeIfAllowed( )閉包。


void (^completeIfAllowed)(void) = ^{
    if (selfCompleted && activeDisposables.count == 0) {
        [subscriber sendCompleted];
        subscribeToSignal = nil;
    }
};


當(dāng)selfCompleted = YES 并且activeDisposables數(shù)組里面的信號(hào)都發(fā)送完畢叶骨,沒有可以發(fā)送的信號(hào)了茫多,即activeDisposables.count = 0,那么就給訂閱者sendCompleted忽刽。這里值得一提的是天揖,還需要把subscribeToSignal手動(dòng)置為nil。因?yàn)樵趕ubscribeToSignal閉包中強(qiáng)引用了completeIfAllowed閉包跪帝,防止completeIfAllowed閉包被提早的銷毀掉了今膊。所以在completeIfAllowed閉包執(zhí)行完畢的時(shí)候,需要再把subscribeToSignal閉包置為nil伞剑。

那么接下來需要看的重點(diǎn)就是subscribeToSignal( )閉包斑唬。


    recur = subscribeToSignal = ^(RACSignal *signal) {
        RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];
        // 1
        @synchronized (subscriber) {
            [compoundDisposable addDisposable:serialDisposable];
            [activeDisposables addObject:serialDisposable];
        }
    
        serialDisposable.disposable = [signal subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            // 2
            __strong void (^subscribeToSignal)(RACSignal *) = recur;
            RACSignal *nextSignal;
            // 3
            @synchronized (subscriber) {
                [compoundDisposable removeDisposable:serialDisposable];
                [activeDisposables removeObjectIdenticalTo:serialDisposable];
                // 4
                if (queuedSignals.count == 0) {
                    completeIfAllowed();
                    return;
                }
                // 5
                nextSignal = queuedSignals[0];
                [queuedSignals removeObjectAtIndex:0];
            }
            // 6
            subscribeToSignal(nextSignal);
        }];
    };



  1. activeDisposables先添加當(dāng)前高階信號(hào)發(fā)出來的信號(hào)的Disposable( 也就是入?yún)⑿盘?hào)的Disposable)
  2. 這里會(huì)對(duì)recur進(jìn)行__strong,因?yàn)橄旅娴?步會(huì)用到subscribeToSignal( )閉包黎泣,同樣也是為了防止出現(xiàn)循環(huán)引用赖钞。
  3. 訂閱入?yún)⑿盘?hào),給訂閱者發(fā)送信號(hào)聘裁。當(dāng)發(fā)送完畢后,activeDisposables中移除它對(duì)應(yīng)的Disposable弓千。
  4. 如果當(dāng)前緩存的queuedSignals數(shù)組里面沒有緩存的信號(hào)衡便,那么就調(diào)用completeIfAllowed( )閉包。
  5. 如果當(dāng)前緩存的queuedSignals數(shù)組里面有緩存的信號(hào)洋访,那么就取出第0個(gè)信號(hào)镣陕,并在queuedSignals數(shù)組移除它。
  6. 把第4步取出的信號(hào)繼續(xù)訂閱姻政,繼續(xù)調(diào)用subscribeToSignal( )閉包呆抑。

總結(jié)一下:高階信號(hào)每發(fā)送一個(gè)信號(hào)值,判斷activeDisposables數(shù)組裝的個(gè)數(shù)是否已經(jīng)超過了maxConcurrent汁展。如果裝不下了就緩存進(jìn)queuedSignals數(shù)組中鹊碍。如果還可以裝的下就開始調(diào)用subscribeToSignal( )閉包厌殉,訂閱當(dāng)前信號(hào)。

每發(fā)送完一個(gè)信號(hào)就判斷緩存數(shù)組queuedSignals的個(gè)數(shù)侈咕,如果緩存數(shù)組里面已經(jīng)沒有信號(hào)了公罕,那么就結(jié)束原來高階信號(hào)的發(fā)送。如果緩存數(shù)組里面還有信號(hào)就繼續(xù)訂閱耀销。如此循環(huán)楼眷,直到原高階信號(hào)所有的信號(hào)都發(fā)送完畢。

整個(gè)flatten:的執(zhí)行流程都分析清楚了熊尉,最后罐柳,關(guān)于入?yún)axConcurrent進(jìn)行更進(jìn)一步的解讀。

回看上面flatten:的實(shí)現(xiàn)中有這樣一句話:


if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) 

那么maxConcurrent的值域就是最終決定flatten:表現(xiàn)行為狰住。

如果maxConcurrent < 0张吉,會(huì)發(fā)生什么?程序會(huì)崩潰转晰。因?yàn)樵谠创a中有這樣一行的初始化的代碼:


NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent];

activeDisposables在初始化的時(shí)候會(huì)初始化一個(gè)大小為maxConcurrent的NSMutableArray芦拿。如果maxConcurrent < 0,那么這里初始化就會(huì)崩潰查邢。

如果maxConcurrent = 0蔗崎,會(huì)發(fā)生什么?那么flatten:就退化成flatten了扰藕。

如果maxConcurrent = 1缓苛,會(huì)發(fā)生什么?那么flatten:就退化成concat了邓深。

如果maxConcurrent > 1未桥,會(huì)發(fā)生什么?由于至今還沒有遇到能用到maxConcurrent > 1的需求情況芥备,所以這里暫時(shí)不展示圖解了冬耿。maxConcurrent > 1之后,flatten的行為還依照高階信號(hào)的個(gè)數(shù)和maxConcurrent的關(guān)系萌壳。如果高階信號(hào)的個(gè)數(shù)<=maxConcurrent的值亦镶,那么flatten:又退化成flatten了。如果高階信號(hào)的個(gè)數(shù)>maxConcurrent的值袱瓮,那么多的信號(hào)就會(huì)進(jìn)入queuedSignals緩存數(shù)組缤骨。

4. concat

這里的concat實(shí)現(xiàn)是在RACSignal里面定義的。


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

一看源碼就知道了尺借,concat其實(shí)就是flatten:1绊起。

當(dāng)然在RACSignal中定義了concat:方法,這個(gè)方法在之前的文章已經(jīng)分析過了燎斩,這里回顧對(duì)比一下:


- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];

        RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            RACDisposable *concattedDisposable = [signal subscribe:subscriber];
            serialDisposable.disposable = concattedDisposable;
        }];

        serialDisposable.disposable = sourceDisposable;
        return serialDisposable;
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}

經(jīng)過對(duì)比可以發(fā)現(xiàn)虱歪,雖然最終變換出來的結(jié)果類似蜂绎,但是針對(duì)的信號(hào)的對(duì)象是不同的,concat是針對(duì)高階信號(hào)進(jìn)行降階操作实蔽。concat:是把兩個(gè)信號(hào)連接起來的操作荡碾。如果把高階信號(hào)按照時(shí)間軸,從左往右局装,依次把每個(gè)信號(hào)都concat:連接起來坛吁,那么結(jié)果就是concat。

5. switchToLatest


- (RACSignal *)switchToLatest {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACMulticastConnection *connection = [self publish];
        
        RACDisposable *subscriptionDisposable = [[connection.signal
                                                  flattenMap:^(RACSignal *x) {
                                                      NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);
                                                      return [x takeUntil:[connection.signal concat:[RACSignal never]]];
                                                  }]
                                                 subscribe:subscriber];
        
        RACDisposable *connectionDisposable = [connection connect];
        return [RACDisposable disposableWithBlock:^{
            [subscriptionDisposable dispose];
            [connectionDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -switchToLatest", self.name];
}

switchToLatest這個(gè)操作只能用在高階信號(hào)上铐尚,如果原信號(hào)里面有不是信號(hào)的值拨脉,那么就會(huì)崩潰,崩潰信息如下:


***** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-switchToLatest requires that the source signal (<RACDynamicSignal: 0x608000038ec0> name: ) send signals.

在switchToLatest操作中宣增,先把原信號(hào)轉(zhuǎn)換成熱信號(hào)玫膀,connection.signal就是RACSubject類型的。對(duì)RACSubject進(jìn)行flattenMap:變換爹脾。在flattenMap:變換中帖旨,connection.signal會(huì)先concat:一個(gè)never信號(hào)。這里concat:一個(gè)never信號(hào)的原因是為了內(nèi)部的信號(hào)過早的結(jié)束而導(dǎo)致訂閱者收到complete信號(hào)灵妨。

flattenMap:變換中x也是一個(gè)信號(hào)解阅,對(duì)x進(jìn)行takeUntil:變換,效果就是下一個(gè)信號(hào)到來之前泌霍,x會(huì)一直發(fā)送信號(hào)货抄,一旦下一個(gè)信號(hào)到來,x就會(huì)被取消訂閱朱转,開始訂閱新的信號(hào)蟹地。

一個(gè)高階信號(hào)經(jīng)過switchToLatest降階操作之后,能得到上圖中的信號(hào)藤为。

6. switch: cases: default:

switch: cases: default:源碼實(shí)現(xiàn)如下:



+ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal {
    NSCParameterAssert(signal != nil);
    NSCParameterAssert(cases != nil);
    
    for (id key in cases) {
        id value __attribute__((unused)) = cases[key];
        NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value);
    }
    
    NSDictionary *copy = [cases copy];
    
    return [[[signal
              map:^(id key) {
                  if (key == nil) key = RACTupleNil.tupleNil;
                  
                  RACSignal *signal = copy[key] ?: defaultSignal;
                  if (signal == nil) {
                      NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key];
                      return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]];
                  }
                  
                  return signal;
              }]
             switchToLatest]
            setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal];
}


實(shí)現(xiàn)中有3個(gè)斷言怪与,全部都是針對(duì)入?yún)⒌囊蟆H雲(yún)ignal信號(hào)和cases字典都不能是nil缅疟。其次分别,cases字典里面所有key對(duì)應(yīng)的value必須是RACSignal類型的。注意窿吩,defaultSignal是可以為nil的。

接下來的實(shí)現(xiàn)比較簡單错览,對(duì)入?yún)鬟M(jìn)來的signal信號(hào)進(jìn)行map變換纫雁,這里的變換是升階的變換。

signal每次發(fā)送出來的一個(gè)值倾哺,就把這個(gè)值當(dāng)做key值去cases字典里面去查找對(duì)應(yīng)的value轧邪。當(dāng)然value對(duì)應(yīng)的是一個(gè)信號(hào)刽脖。如果value對(duì)應(yīng)的信號(hào)不為空,就把signal發(fā)送出來的這個(gè)值map成字典里面對(duì)應(yīng)的信號(hào)忌愚。如果value對(duì)應(yīng)為空曲管,那么就把原signal發(fā)出來的值map成defaultSignal信號(hào)。

如果經(jīng)過轉(zhuǎn)換之后硕糊,得到的信號(hào)為nil院水,就會(huì)返回一個(gè)error信號(hào)。如果得到的信號(hào)不為nil简十,那么原信號(hào)完全轉(zhuǎn)換完成就會(huì)變成一個(gè)高階信號(hào)檬某,這個(gè)高階信號(hào)里面裝的都是信號(hào)。最后再對(duì)這個(gè)高階信號(hào)執(zhí)行switchToLatest轉(zhuǎn)換螟蝙。

7. if: then: else:

if: then: else:源碼實(shí)現(xiàn)如下:



+ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal {
    NSCParameterAssert(boolSignal != nil);
    NSCParameterAssert(trueSignal != nil);
    NSCParameterAssert(falseSignal != nil);
    
    return [[[boolSignal
              map:^(NSNumber *value) {
                  NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value);
                  
                  return (value.boolValue ? trueSignal : falseSignal);
              }]
             switchToLatest]
            setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal];
}


入?yún)oolSignal恢恼,trueSignal,falseSignal三個(gè)信號(hào)都不能為nil胰默。

boolSignal里面都必須裝的是NSNumber類型的值场斑。

針對(duì)boolSignal進(jìn)行map升階操作,boolSignal信號(hào)里面的值如果是YES牵署,那么就轉(zhuǎn)換成trueSignal信號(hào)漏隐,如果為NO,就轉(zhuǎn)換成falseSignal碟刺。升階轉(zhuǎn)換完成之后锁保,boolSignal就是一個(gè)高階信號(hào),然后再進(jìn)行switchToLatest操作半沽。

8. catch:

catch:的實(shí)現(xiàn)如下:



- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock {
    NSCParameterAssert(catchBlock != NULL);
    
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init];
        
        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            RACSignal *signal = catchBlock(error);
            NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self);
            catchDisposable.disposable = [signal subscribe:subscriber];
        } completed:^{
            [subscriber sendCompleted];
        }];
        
        return [RACDisposable disposableWithBlock:^{
            [catchDisposable dispose];
            [subscriptionDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -catch:", self.name];
}

當(dāng)對(duì)原信號(hào)進(jìn)行訂閱的時(shí)候爽柒,如果出現(xiàn)了錯(cuò)誤,會(huì)去執(zhí)行catchBlock( )閉包者填,入?yún)閯倓偖a(chǎn)生的error浩村。catchBlock( )閉包產(chǎn)生的是一個(gè)新的RACSignal,并再次用訂閱者訂閱該信號(hào)占哟。

這里之所以說是高階操作心墅,是因?yàn)檫@里原信號(hào)發(fā)生錯(cuò)誤之后,錯(cuò)誤會(huì)升階成一個(gè)信號(hào)榨乎。

9. catchTo:

catchTo:的實(shí)現(xiàn)如下:


- (RACSignal *)catchTo:(RACSignal *)signal {
    return [[self catch:^(NSError *error) {
        return signal;
    }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal];
}

catchTo:的實(shí)現(xiàn)就是調(diào)用catch:方法怎燥,只不過原來catch:方法里面的catchBlock( )閉包,永遠(yuǎn)都只返回catchTo:的入?yún)⒚凼睿瑂ignal信號(hào)铐姚。

10. try:


- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock {
    NSCParameterAssert(tryBlock != NULL);
    
    return [[self flattenMap:^(id value) {
        NSError *error = nil;
        BOOL passed = tryBlock(value, &error);
        return (passed ? [RACSignal return:value] : [RACSignal error:error]);
    }] setNameWithFormat:@"[%@] -try:", self.name];
}

try:可以用來進(jìn)來信號(hào)的升階操作。對(duì)原信號(hào)進(jìn)行flattenMap變換肛捍,對(duì)信號(hào)發(fā)出來的每個(gè)值都調(diào)用一遍tryBlock( )閉包隐绵,如果這個(gè)閉包的返回值是YES之众,那么就返回[RACSignal return:value],如果閉包的返回值是NO依许,那么就返回error棺禾。原信號(hào)中如果都是值,那么經(jīng)過try:操作之后峭跳,每個(gè)值都會(huì)變成RACSignal膘婶,于是原信號(hào)也就變成了高階信號(hào)了。

當(dāng)然坦康,如果在block的實(shí)現(xiàn)中返回一個(gè)信號(hào)竣付,這時(shí)就不會(huì)升階了。返回的信號(hào)里面可以不返回信號(hào)滞欠,而是直接返回值古胆。

11. tryMap:


- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock {
    NSCParameterAssert(mapBlock != NULL);
    
    return [[self flattenMap:^(id value) {
        NSError *error = nil;
        id mappedValue = mapBlock(value, &error);
        return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]);
    }] setNameWithFormat:@"[%@] -tryMap:", self.name];
}

tryMap:的實(shí)現(xiàn)和try:的實(shí)現(xiàn)基本一致,唯一不同的就是入?yún)㈤]包的返回值不同筛璧。在tryMap:中調(diào)用mapBlock( )閉包逸绎,返回是一個(gè)對(duì)象,如果這個(gè)對(duì)象不為nil夭谤,就返回[RACSignal return:mappedValue]棺牧。如果返回的對(duì)象是nil,那么就變換成error信號(hào)朗儒。

12. timeout: onScheduler:



- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
    NSCParameterAssert(scheduler != nil);
    NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);
    
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
        
        RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{
            [disposable dispose];
            [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]];
        }];
        
        [disposable addDisposable:timeoutDisposable];
        
        RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [disposable dispose];
            [subscriber sendError:error];
        } completed:^{
            [disposable dispose];
            [subscriber sendCompleted];
        }];
        
        [disposable addDisposable:subscriptionDisposable];
        return disposable;
    }] setNameWithFormat:@"[%@] -timeout: %f onScheduler: %@", self.name, (double)interval, scheduler];
}


timeout: onScheduler:的實(shí)現(xiàn)很簡單颊乘,它比正常的信號(hào)訂閱多了一個(gè)timeoutDisposable操作。它在信號(hào)訂閱的內(nèi)部開啟了一個(gè)scheduler醉锄,經(jīng)過interval的時(shí)間之后乏悄,就會(huì)停止訂閱原信號(hào),并對(duì)訂閱者sendError恳不。

這個(gè)操作的表意和方法名完全一致檩小,經(jīng)過interval的時(shí)間之后,就算timeout烟勋,那么就停止訂閱原信號(hào)规求,并sendError。

總結(jié)一下ReactiveCocoa v2.5中高階信號(hào)的升階 / 降階操作:

升階操作

  1. map( 把值map成一個(gè)信號(hào))
  2. [RACSignal return:signal]

降階操作

  1. flatten(等效于flatten:0卵惦,+merge:)
  2. concat(等效于flatten:1)
  3. flatten:1
  4. switchToLatest
  5. flattenMap:

這5種操作能將高階信號(hào)變?yōu)榈碗A信號(hào)阻肿,但是最終降階之后的效果就只有3種:switchToLatest,flatten沮尿,concat丛塌。具體的圖示見上面的分析。

二. 同步操作

在ReactiveCocoa中還包含一些同步的操作,這些操作一般我們很少使用姨伤,除非真的很確定這樣做了之后不會(huì)有什么問題,否則胡亂使用會(huì)導(dǎo)致線程死鎖等一些嚴(yán)重的問題庸疾。

1. firstOrDefault: success: error:


- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error {
    NSCondition *condition = [[NSCondition alloc] init];
    condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue];
    
    __block id value = defaultValue;
    __block BOOL done = NO;
    
    // Ensures that we don't pass values across thread boundaries by reference.
    __block NSError *localError;
    __block BOOL localSuccess;
    
    [[self take:1] subscribeNext:^(id x) {
        // 加鎖
        [condition lock];
        
        value = x;
        localSuccess = YES;
        
        done = YES;
        [condition broadcast];
        // 解鎖
        [condition unlock];
    } error:^(NSError *e) {
        // 加鎖
        [condition lock];
        
        if (!done) {
            localSuccess = NO;
            localError = e;
            
            done = YES;
            [condition broadcast];
        }
        // 解鎖
        [condition unlock];
    } completed:^{
        // 加鎖
        [condition lock];
        
        localSuccess = YES;
        
        done = YES;
        [condition broadcast];
        // 解鎖
        [condition unlock];
    }];
    // 加鎖
    [condition lock];
    while (!done) {
        [condition wait];
    }
    
    if (success != NULL) *success = localSuccess;
    if (error != NULL) *error = localError;
    // 解鎖
    [condition unlock];
    return value;
}



從源碼上看乍楚,firstOrDefault: success: error:這種同步的方法很容易導(dǎo)致線程死鎖胡岔。它在subscribeNext衙传,error习柠,completed的閉包里面都調(diào)用condition鎖先lock再unlock鸟妙。如果一個(gè)信號(hào)發(fā)送值過來国瓮,都沒有執(zhí)行subscribeNext醒颖,error走哺,completed這3個(gè)操作里面的任意一個(gè)坏平,那么就會(huì)執(zhí)行[condition wait]揍拆,等待渠概。

由于對(duì)原信號(hào)進(jìn)行了take:1操作,所以只會(huì)對(duì)第一個(gè)值進(jìn)行操作嫂拴。執(zhí)行完subscribeNext播揪,error,completed這3個(gè)操作里面的任意一個(gè)筒狠,又會(huì)加一次鎖猪狈,對(duì)外部傳進(jìn)來的入?yún)uccess和error進(jìn)行賦值,已便外部可以拿到里面的狀態(tài)辩恼。最終返回信號(hào)是原信號(hào)中第一個(gè)next里面的值雇庙,如果原信號(hào)第一個(gè)值沒有,比如直接error或者completed灶伊,那么返回的是defaultValue疆前。

done為YES表示已經(jīng)成功執(zhí)行了subscribeNext,error谁帕,completed這3個(gè)操作里面的任意一個(gè)峡继。反之為NO。

localSuccess為YES表示成功發(fā)送值或者成功發(fā)送完了原信號(hào)的所有值匈挖,期間沒有發(fā)生錯(cuò)誤碾牌。

condition的broadcast操作是喚醒其他線程的操作,相當(dāng)于操作系統(tǒng)里面互斥信號(hào)量的signal操作儡循。

入?yún)efaultValue是給內(nèi)部變量value的一個(gè)初始值舶吗。當(dāng)原信號(hào)發(fā)送出一個(gè)值之后,value的值時(shí)刻都會(huì)與原信號(hào)的值保持一致择膝。

success和error是外部變量的地址誓琼,從外面可以監(jiān)聽到里面的狀態(tài)。在函數(shù)內(nèi)部賦值,在函數(shù)外面拿到它們的值腹侣。

2. firstOrDefault:


- (id)firstOrDefault:(id)defaultValue {
    return [self firstOrDefault:defaultValue success:NULL error:NULL];
}


firstOrDefault:的實(shí)現(xiàn)就是調(diào)用了firstOrDefault: success: error:方法叔收。只不過不需要傳success和error,不關(guān)心內(nèi)部的狀態(tài)傲隶。最終返回信號(hào)是原信號(hào)中第一個(gè)next里面的值饺律,如果原信號(hào)第一個(gè)值沒有,比如直接error或者completed跺株,那么返回的是defaultValue复濒。

3. first


- (id)first {
    return [self firstOrDefault:nil];
}

first方法就更加省略,連defaultValue也不傳乒省。最終返回信號(hào)是原信號(hào)中第一個(gè)next里面的值巧颈,如果原信號(hào)第一個(gè)值沒有,比如直接error或者completed袖扛,那么返回的是nil砸泛。

4. waitUntilCompleted:



- (BOOL)waitUntilCompleted:(NSError **)error {
    BOOL success = NO;
    
    [[[self
       ignoreValues]
      setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name]
     firstOrDefault:nil success:&success error:error];
    
    return success;
}

waitUntilCompleted:里面還是調(diào)用firstOrDefault: success: error:方法。返回值是success蛆封。只要原信號(hào)正常的發(fā)送完信號(hào)晾嘶,success應(yīng)該為YES,但是如果發(fā)送過程中出現(xiàn)了error娶吞,success就為NO垒迂。success作為返回值,外部就可以監(jiān)聽到是否發(fā)送成功妒蛇。

雖然這個(gè)方法可以監(jiān)聽到發(fā)送結(jié)束的狀態(tài)机断,但是也盡量不要使用,因?yàn)樗膶?shí)現(xiàn)調(diào)用了firstOrDefault: success: error:方法绣夺,這個(gè)方法里面有大量的鎖的操作吏奸,一不留神就會(huì)導(dǎo)致死鎖。

5. toArray


- (NSArray *)toArray {
    return [[[self collect] first] copy];
}


經(jīng)過collect之后陶耍,原信號(hào)所有的值都會(huì)被加到一個(gè)數(shù)組里面奋蔚,取出信號(hào)的第一個(gè)值就是一個(gè)數(shù)組。所以執(zhí)行完first之后第一個(gè)值就是原信號(hào)所有值的數(shù)組烈钞。

三. 副作用操作

ReactiveCocoa v2.5中還為我們提供了一些可以進(jìn)行副作用操作的函數(shù)泊碑。

1. doNext:


- (RACSignal *)doNext:(void (^)(id x))block {
    NSCParameterAssert(block != NULL);
    
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            block(x);
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -doNext:", self.name];
}

doNext:能讓我們?cè)谠盘?hào)sendNext之前,能執(zhí)行一個(gè)block閉包毯欣,在這個(gè)閉包中我們可以執(zhí)行我們想要執(zhí)行的副作用操作馒过。

2. doError:


- (RACSignal *)doError:(void (^)(NSError *error))block {
    NSCParameterAssert(block != NULL);
    
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            block(error);
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -doError:", self.name];
}


doError:能讓我們?cè)谠盘?hào)sendError之前,能執(zhí)行一個(gè)block閉包酗钞,在這個(gè)閉包中我們可以執(zhí)行我們想要執(zhí)行的副作用操作腹忽。

3. doCompleted:



- (RACSignal *)doCompleted:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            block();
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -doCompleted:", self.name];
}



doCompleted:能讓我們?cè)谠盘?hào)sendCompleted之前来累,能執(zhí)行一個(gè)block閉包,在這個(gè)閉包中我們可以執(zhí)行我們想要執(zhí)行的副作用操作窘奏。

4. initially:


- (RACSignal *)initially:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    return [[RACSignal defer:^{
        block();
        return self;
    }] setNameWithFormat:@"[%@] -initially:", self.name];
}

initially:能讓我們?cè)谠盘?hào)發(fā)送之前嘹锁,先調(diào)用了defer:操作,在return self之前先執(zhí)行了一個(gè)閉包着裹,在這個(gè)閉包中我們可以執(zhí)行我們想要執(zhí)行的副作用操作兼耀。

5. finally:


- (RACSignal *)finally:(void (^)(void))block {
    NSCParameterAssert(block != NULL);
    
    return [[[self
              doError:^(NSError *error) {
                  block();
              }]
             doCompleted:^{
                 block();
             }]
            setNameWithFormat:@"[%@] -finally:", self.name];
}


finally:操作調(diào)用了doError:和doCompleted:操作,依次在sendError之前求冷,sendCompleted之前,插入一個(gè)block( )閉包窍霞。這樣當(dāng)信號(hào)因?yàn)殄e(cuò)誤而要終止取消訂閱匠题,或者,發(fā)送結(jié)束之前但金,都能執(zhí)行一段我們想要執(zhí)行的副作用操作韭山。

四. 多線程操作

在RACSignal里面有3個(gè)關(guān)于多線程的操作。

1. deliverOn:



- (RACSignal *)deliverOn:(RACScheduler *)scheduler {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            [scheduler schedule:^{
                [subscriber sendNext:x];
            }];
        } error:^(NSError *error) {
            [scheduler schedule:^{
                [subscriber sendError:error];
            }];
        } completed:^{
            [scheduler schedule:^{
                [subscriber sendCompleted];
            }];
        }];
    }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler];
}


deliverOn:的入?yún)⑹且粋€(gè)scheduler冷溃,當(dāng)原信號(hào)subscribeNext钱磅,sendError,sendCompleted的時(shí)候似枕,都去調(diào)用scheduler的schedule方法盖淡。



- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];

    block();
    return nil;
}

在schedule的方法里面會(huì)判斷當(dāng)前currentScheduler是否為nil,如果是nil就調(diào)用backgroundScheduler去執(zhí)行block( )閉包,如果不為nil凿歼,當(dāng)前currentScheduler直接執(zhí)行block( )閉包褪迟。



+ (instancetype)currentScheduler {
    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
    if (scheduler != nil) return scheduler;
    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

    return nil;
}

判斷currentScheduler是否存在,看兩點(diǎn)答憔,一是當(dāng)前線程的字典里面味赃,是否存在RACSchedulerCurrentSchedulerKey( @"RACSchedulerCurrentSchedulerKey" ),如果存在對(duì)應(yīng)的value虐拓,返回scheduler心俗,二是看當(dāng)前的類是不是在主線程,如果在主線程蓉驹,返回mainThreadScheduler城榛。如果兩個(gè)條件都不存在,那么當(dāng)前currentScheduler就不存在态兴,返回nil吠谢。

deliverOn:操作的特點(diǎn)是原信號(hào)發(fā)送sendNext,sendError诗茎,sendCompleted所在線程是確定的工坊。

2. subscribeOn:



- (RACSignal *)subscribeOn:(RACScheduler *)scheduler {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
        
        RACDisposable *schedulingDisposable = [scheduler schedule:^{
            RACDisposable *subscriptionDisposable = [self subscribe:subscriber];
            
            [disposable addDisposable:subscriptionDisposable];
        }];
        
        [disposable addDisposable:schedulingDisposable];
        return disposable;
    }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler];
}


subscribeOn:操作就是在傳入的scheduler的閉包內(nèi)部訂閱原信號(hào)的献汗。它與deliverOn:操作就不同:

subscribeOn:操作能夠保證didSubscribe block( )閉包在入?yún)cheduler中執(zhí)行,但是不能保證原信號(hào)subscribeNext王污,sendError罢吃,sendCompleted在哪個(gè)scheduler中執(zhí)行。

deliverOn:與subscribeOn:正好反過來昭齐,能保證原信號(hào)subscribeNext尿招,sendError,sendCompleted在哪個(gè)scheduler中執(zhí)行阱驾,但是不能保證didSubscribe block( )閉包在哪個(gè)scheduler中執(zhí)行就谜。

3. deliverOnMainThread



- (RACSignal *)deliverOnMainThread {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        __block volatile int32_t queueLength = 0;
        
        void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { // 暫時(shí)省略};
        
        return [self subscribeNext:^(id x) {
            performOnMainThread(^{
                [subscriber sendNext:x];
            });
        } error:^(NSError *error) {
            performOnMainThread(^{
                [subscriber sendError:error];
            });
        } completed:^{
            performOnMainThread(^{
                [subscriber sendCompleted];
            });
        }];
    }] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name];
}


對(duì)比deliverOn:的源碼實(shí)現(xiàn),發(fā)現(xiàn)兩者比較相似里覆,只不過這里deliverOnMainThread把sendNext丧荐,sendError,sendCompleted都包在了performOnMainThread閉包中執(zhí)行喧枷。


        __block volatile int32_t queueLength = 0;
        
        void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) {
            int32_t queued = OSAtomicIncrement32(&queueLength);
            if (NSThread.isMainThread && queued == 1) {
                block();
                OSAtomicDecrement32(&queueLength);
            } else {
                dispatch_async(dispatch_get_main_queue(), ^{
                    block();
                    OSAtomicDecrement32(&queueLength);
                });
            }
        };

performOnMainThread閉包內(nèi)部保證了入?yún)lock( )閉包一定是在主線程中執(zhí)行虹统。

OSAtomicIncrement32 和 OSAtomicDecrement32是原子操作,分別代表+1和-1隧甚。下面的if-else判斷里面车荔,不管是滿足哪一條,最終都還是在主線程中執(zhí)行block( )閉包戚扳。

deliverOnMainThread能保證原信號(hào)subscribeNext忧便,sendError,sendCompleted都在主線程MainThread中執(zhí)行帽借。

五. 其他操作

1. setKeyPath: onObject: nilValue:

setKeyPath: onObject: nilValue: 的源碼實(shí)現(xiàn)如下:


- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue {
    NSCParameterAssert(keyPath != nil);
    NSCParameterAssert(object != nil);
    
    keyPath = [keyPath copy];
    
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    
    __block void * volatile objectPtr = (__bridge void *)object;
    
    RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
        // 1
        __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;
        [object setValue:x ?: nilValue forKeyPath:keyPath];
    } error:^(NSError *error) {
        __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;
        
        NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);
        NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);
        
        [disposable dispose];
    } completed:^{
        [disposable dispose];
    }];
    
    [disposable addDisposable:subscriptionDisposable];
    
#if DEBUG
    static void *bindingsKey = &bindingsKey;
    NSMutableDictionary *bindings;
    
    @synchronized (object) {
        // 2
        bindings = objc_getAssociatedObject(object, bindingsKey);
        if (bindings == nil) {
            bindings = [NSMutableDictionary dictionary];
            objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
    
    @synchronized (bindings) {
        NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self);
        
        bindings[keyPath] = [NSValue valueWithNonretainedObject:self];
    }
#endif
    
    RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{
#if DEBUG
        @synchronized (bindings) {
            // 3
            [bindings removeObjectForKey:keyPath];
        }
#endif
        
        while (YES) {
            void *ptr = objectPtr;
            // 4
            if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) {
                break;
            }
        }
    }];
    
    [disposable addDisposable:clearPointerDisposable];
    
    [object.rac_deallocDisposable addDisposable:disposable];
    
    RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable;
    return [RACDisposable disposableWithBlock:^{
        [objectDisposable removeDisposable:disposable];
        [disposable dispose];
    }];
}


代碼雖然有點(diǎn)長茬腿,但是逐行讀下來不是很難,需要注意的有4點(diǎn)地方宜雀,已經(jīng)在上述代碼里面標(biāo)明了切平。接下來一一分析。

1. objc_precise_lifetime的問題辐董。

作者在這里寫了一段注釋:

Possibly spec, possibly compiler bug, but this __bridge cast does not result in a retain here, effectively an invisible __unsafe_unretained qualifier. Using objc_precise_lifetime gives the __strong reference desired. The explicit use of __strong is strictly defensive.

作者懷疑是編譯器的一個(gè)bug悴品,即使是顯示的調(diào)用了__strong,依舊沒法保證被強(qiáng)引用了简烘,所以還需要用objc_precise_lifetime來保證強(qiáng)引用苔严。

關(guān)于這個(gè)問題,筆者查詢了一下LLVM的文檔孤澎,在6.3 precise lifetime semantics這一節(jié)中提到了這個(gè)問題届氢。

通常上,凡是聲明了__strong的變量覆旭,都會(huì)有很確切的生命周期退子。ARC會(huì)維持這些__strong的變量在其生命周期中被retained岖妄。

但是自動(dòng)存儲(chǔ)的局部變量是沒有確切的生命周期的。這些變量僅僅只是簡單的持有一個(gè)強(qiáng)引用寂祥,強(qiáng)引用著retain對(duì)象的指針類型的值荐虐。這些值完全受控于本地控制者的如何優(yōu)化。所以要想改變這些局部變量的生命周期丸凭,是不可能的事情福扬。因?yàn)橛刑嗟膬?yōu)化,理論上都會(huì)導(dǎo)致局部變量的生命周期減少惜犀,但是這些優(yōu)化非常有用铛碑。

但是LLVM為我們提供了一個(gè)關(guān)鍵字objc_precise_lifetime,使用這個(gè)可以是局部變量的生命周期變成確切的虽界。這個(gè)關(guān)鍵字有時(shí)候還是非常有用的汽烦。甚至更加極端情況,該局部變量都沒有被使用浓恳,但是它依舊可以保持一個(gè)確定的生命周期。

回到源碼上來碗暗,接著代碼會(huì)對(duì)入?yún)bject進(jìn)行setValue: forKeyPath:


[object setValue:x ?: nilValue forKeyPath:keyPath];

如何x為nil就返回nilValue傳進(jìn)來的值颈将。

2. AssociatedObject關(guān)聯(lián)對(duì)象

如果bindings字典不存在,那么就調(diào)用objc_setAssociatedObject對(duì)object進(jìn)行關(guān)聯(lián)對(duì)象言疗。參數(shù)是OBJC_ASSOCIATION_RETAIN_NONATOMIC晴圾。如果bindings字典存在,就用objc_getAssociatedObject取出字典噪奄。

在字典里面重新更新綁定key-value值死姚,key就是入?yún)eyPath,value是原信號(hào)勤篮。

3. 取消訂閱原信號(hào)的時(shí)候

[bindings removeObjectForKey:keyPath];

當(dāng)信號(hào)取消訂閱的時(shí)候都毒,移除所有的關(guān)聯(lián)值。

3. OSAtomicCompareAndSwapPtrBarrier

這個(gè)函數(shù)屬于OSAtomic原子操作碰缔,原型如下:


OSAtomicCompareAndSwapPtrBarrier(type __oldValue, type __newValue, volatile type *__theValue)

Compares a variable against the specified old value. If the two values are equal, this function assigns the specified new value to the variable; otherwise, it does nothing. The comparison and assignment are done as one atomic operation and the function returns a Boolean value indicating whether the swap actually occurred.

這個(gè)函數(shù)用于比較__oldValue是否與__theValue指針指向的內(nèi)存位置的值匹配账劲,如果匹配,則將__newValue的值存儲(chǔ)到__theValue指向的內(nèi)存位置金抡。整個(gè)函數(shù)的返回值就是交換是否成功的BOOL值瀑焦。

    while (YES) {
    void *ptr = objectPtr;
    if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr))   {
          break;
    }
  }

在這個(gè)while的死循環(huán)里面只有當(dāng)OSAtomicCompareAndSwapPtrBarrier返回值為YES,才能退出整個(gè)死循環(huán)梗肝。返回值為YES就代表&objectPtr被置為了NULL榛瓮,這樣就確保了在線程安全的情況下,不存在野指針的問題了巫击。

2. setKeyPath: onObject:


- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object {
    return [self setKeyPath:keyPath onObject:object nilValue:nil];
}

setKeyPath: onObject:就是調(diào)用setKeyPath: onObject: nilValue:方法禀晓,只不過nilValue傳遞的是nil精续。

最后

關(guān)于RACSignal的所有操作底層分析實(shí)現(xiàn)都已經(jīng)分析完成。最后請(qǐng)大家多多指教匆绣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驻右,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子崎淳,更是在濱河造成了極大的恐慌堪夭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拣凹,死亡現(xiàn)場離奇詭異森爽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嚣镜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門爬迟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人菊匿,你說我怎么就攤上這事付呕。” “怎么了跌捆?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵徽职,是天一觀的道長。 經(jīng)常有香客問我佩厚,道長姆钉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任抄瓦,我火速辦了婚禮潮瓶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钙姊。我一直安慰自己毯辅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布煞额。 她就那樣靜靜地躺著悉罕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪立镶。 梳的紋絲不亂的頭發(fā)上壁袄,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音媚媒,去河邊找鬼嗜逻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缭召,可吹牛的內(nèi)容都是我干的栈顷。 我是一名探鬼主播逆日,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼萄凤!你這毒婦竟也來了室抽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤靡努,失蹤者是張志新(化名)和其女友劉穎坪圾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惑朦,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兽泄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漾月。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病梢。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖梁肿,靈堂內(nèi)的尸體忽然破棺而出蜓陌,到底是詐尸還是另有隱情,我是刑警寧澤吩蔑,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布钮热,位于F島的核電站,受9級(jí)特大地震影響哥纫,放射性物質(zhì)發(fā)生泄漏霉旗。R本人自食惡果不足惜痴奏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一蛀骇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧读拆,春花似錦擅憔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辟灰,卻和暖如春个榕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芥喇。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工西采, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人继控。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓械馆,卻偏偏與公主長得像胖眷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霹崎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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