RACSignal戏仓、RACSequence疚宇、RACTuple

(大喇叭:)本篇比較長(zhǎng)請(qǐng)耐心閱讀,主要解釋一下RACSignal的部分內(nèi)容赏殃,詳細(xì)介紹了RACSequence和RACTuple的原理敷待,介紹了Push-Driven和Pull-Driven。

簡(jiǎn)介

ReactiveCocoa是由git開(kāi)源的應(yīng)用于iOS和OS開(kāi)發(fā)的重型FRP (Functional Reactive Programming 是一種響應(yīng)變化的編程范式) 框架仁热。內(nèi)部使用了大量的block榜揖。FRP的核心就是信號(hào)。

RACStream

This class represents a monad, upon which many stream-based operations can be built.

RACStream是一種流抗蠢。本身并沒(méi)有特別大的用處举哟,主要在兩個(gè)子類(lèi)RACSignal和RACSequence上得到體現(xiàn)。

由于RACStream是一個(gè)根類(lèi)迅矛,所以暫時(shí)先不做介紹妨猩,它的一些方法主要會(huì)用在RACSignal中。先把周邊介紹一下在回過(guò)頭來(lái)看秽褒,就不會(huì)那么害怕了壶硅。

RACSignal

RACSignal是ReactiveCocoa中的重要概念,是一種信號(hào)流销斟,繼承于RACStream庐椒。信號(hào)就是數(shù)據(jù)流,可以用來(lái)傳遞和綁定蚂踊。

這個(gè)RACSignal通常是冷信號(hào)(關(guān)于冷信號(hào)與熱信號(hào)之后章節(jié)中再詳細(xì)說(shuō)明)约谈。

這里引用一下這里的解釋?zhuān)杏X(jué)很形象。

使用ReactiveCocoa實(shí)現(xiàn)iOS平臺(tái)響應(yīng)式編程

把信號(hào)想象成水龍頭,只不過(guò)里面不是水窗宇,而是玻璃球(value)措伐,直徑跟水管的內(nèi)徑一樣,這樣就能抱枕玻璃球是依次排列军俊,不會(huì)出現(xiàn)并排的情況(數(shù)據(jù)都是線(xiàn)性處理的侥加,不會(huì)出現(xiàn)并發(fā)情況)。水龍頭的開(kāi)放默認(rèn)是關(guān)的粪躬,除非有了接收方(subscriber)担败,才會(huì)重新打開(kāi)。這樣只要有新的玻璃球進(jìn)來(lái)镰官,就會(huì)自動(dòng)傳送給接收方提前。

創(chuàng)建一個(gè)信號(hào) +createSignal:

通常情況下,我們使用[RACSignal createSignal:]來(lái)創(chuàng)建信號(hào)泳唠。
信號(hào)的發(fā)送過(guò)程查看之前的文章:ReactiveCocoa信號(hào)發(fā)送詳解

RACSignal會(huì)發(fā)送三種信號(hào)狀態(tài)狈网,sendNext:sendError:笨腥, sendCompleted拓哺。

  1. sendNext:可以發(fā)送多個(gè)next,subscriber會(huì)依次接受發(fā)送的數(shù)據(jù)脖母。
  2. sendError:發(fā)送錯(cuò)誤士鸥,為NSError對(duì)象。
  3. sendCompleted:發(fā)送成功谆级,整個(gè)流程完成烤礁,不會(huì)再發(fā)送新的next。
    在一個(gè)Racsignal的生命周期中肥照,可以發(fā)送多個(gè)next數(shù)據(jù)脚仔,但是只能發(fā)送一個(gè)Error或者Completed。

+error:

返回一個(gè)信號(hào)建峭,訂閱后玻侥,立即發(fā)送錯(cuò)誤

+never

返回一個(gè)永遠(yuǎn)不會(huì)完成的信號(hào)。

+startEagerlyWithScheduler:block:

+startLazilyWithScheduler:block:

這個(gè)方法跟下面的eagerSequence有點(diǎn)類(lèi)似亿蒸。在沒(méi)有訂閱該信號(hào)的情況下,會(huì)執(zhí)行一次block中的內(nèi)容掌桩。這個(gè)方法通常會(huì)用在異步處理網(wǎng)絡(luò)請(qǐng)求上边锁。詳細(xì)的原理,在理解RACMulticastConnection之后就會(huì)比較清楚了波岛。

RACSignal *signal1 = [[RACSignal startEagerlyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
  NSLog(@"signal 1茅坛、、、贡蓖、曹鸠、、");
  [subscriber sendNext:@"1"];
  [subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];
   
RACSignal *signal2 = [[RACSignal startLazilyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
   NSLog(@"signal 2斥铺、彻桃、、晾蜘、邻眷、、剔交、");
   [subscriber sendNext:@"2"];
   [subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];

//運(yùn)行代碼在沒(méi)有訂閱信號(hào)的時(shí)候會(huì)有輸出,是因?yàn)樵赱RACMulticastConnection connect]中會(huì)有subscribe發(fā)生肆饶。
//ReactiveCocoaTest[8783:14103027] signal 1、岖常、驯镊、、竭鞍、板惑、

+return:

直接返回一個(gè)信號(hào),并發(fā)送一個(gè)value笼蛛。

+empty

返回一個(gè)nil

-concat:

把一個(gè)信號(hào)拼接到另一個(gè)信號(hào)后面洒放,當(dāng)多個(gè)信號(hào)發(fā)送時(shí),有序的接受信號(hào)滨砍。如果第一個(gè)信號(hào)沒(méi)有發(fā)送完成往湿,則后續(xù)的信號(hào)不會(huì)執(zhí)行。

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"signalA"];
    [subscriber sendCompleted];
    return nil;
}];
    
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"signalB"];
    return nil;
}];

// 將兩個(gè)信號(hào)拼接
RACSignal *signal = [signalA concat:signalB];
[signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
// 此時(shí)會(huì)輸出signalA惋戏、signalB领追。如果調(diào)換位置[signalB concat:signalA],則只會(huì)輸出signalB

<b>原理:</b>

  1. 創(chuàng)建了一個(gè)新的信號(hào)响逢。
  2. 這個(gè)新的信號(hào)內(nèi)部中绒窑,訂閱了當(dāng)前的信號(hào)singalA,依次發(fā)送數(shù)據(jù)sendNext:
  3. 當(dāng)發(fā)送完成,執(zhí)行completed舔亭,此時(shí)signalB會(huì)被訂閱者訂閱
- (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];
}

-zipWith:

壓縮信號(hào)些膨,將兩個(gè)信號(hào)壓縮之后,把數(shù)據(jù)合并成一個(gè)RACTuple發(fā)送出去钦铺。

<b>原理:</b>

- (RACSignal *)zipWith:(RACSignal *)signal {
    NSCParameterAssert(signal != nil);
    // 創(chuàng)建一個(gè)新信號(hào)
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        __block BOOL selfCompleted = NO;
        NSMutableArray *selfValues = [NSMutableArray array];

        __block BOOL otherCompleted = NO;
        NSMutableArray *otherValues = [NSMutableArray array];
        // 發(fā)送成功
        void (^sendCompletedIfNecessary)(void) = ^{
            @synchronized (selfValues) {
                BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
                BOOL otherEmpty = (otherCompleted && otherValues.count == 0);   
                // 兩個(gè)信號(hào)都發(fā)送成功則新信號(hào)發(fā)送成功
                if (selfEmpty || otherEmpty) [subscriber sendCompleted];
            }
        };

        // sendNext
        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];
                // 當(dāng)壓縮成一個(gè)RACTuple時(shí)订雾,會(huì)將第一次發(fā)送的數(shù)據(jù)從數(shù)組中刪除
                // 發(fā)送數(shù)據(jù)
                [subscriber sendNext:tuple];
                sendCompletedIfNecessary();
            }
        };
        // 訂閱singalA被訂閱
        RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
            @synchronized (selfValues) {
                [selfValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                selfCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
        // singalB訂閱
        RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {
            @synchronized (selfValues) {
                [otherValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                otherCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];

        return [RACDisposable disposableWithBlock:^{
            [selfDisposable dispose];
            [otherDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}

  1. 創(chuàng)建一個(gè)新信號(hào),singalA和singalB兩個(gè)信號(hào)都會(huì)在內(nèi)部被訂閱矛洞。
  2. 當(dāng)singalA發(fā)送信號(hào)洼哎,會(huì)判斷singalB有沒(méi)有發(fā)送信號(hào),同理,sigbalB發(fā)送信號(hào)時(shí)會(huì)檢測(cè)singalA有沒(méi)有發(fā)送信號(hào)噩峦,當(dāng)兩個(gè)都發(fā)送了信號(hào)之后會(huì)打包成一個(gè)RACTuple锭沟。
  3. sendCompleted也是同理。

訂閱 -subscribe

創(chuàng)建了signal之后识补,本身是不會(huì)做任何處理族淮,必須訂閱這個(gè)信號(hào),才能為我所用李请。
通常情況下使用[signal subscribeNext:]直接訂閱信號(hào)瞧筛,這個(gè)方法等返回值是一個(gè)RACDisposable類(lèi)型的值。也就是我們可以在外部對(duì)這個(gè)信號(hào)進(jìn)行操作导盅,比如取消訂閱[disposable dispose]较幌。

// creat a signal
RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"a"];
    [subscriber sendCompleted];
    // 取消訂閱要調(diào)用這里block塊
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"disposed");
    }];
}] doNext:^(id x) {
    NSLog(@"do next");
}] doCompleted:^{
    NSLog(@"do Completed");
}];

// subscribe do subscription
[signal subscribeNext:^(id x) {
    // subscription
    NSLog(@"subscriber receive value: %@", x);
} completed:^{
    NSLog(@"completed end");
}];

doNext:、doCompleted白翻、doError:

此處只用doNext:來(lái)做說(shuō)明乍炉,其它兩個(gè)與此類(lèi)似。

doNext:是在執(zhí)行sendNext:之前執(zhí)行的操作滤馍,就是訂閱成功之后岛琼,將要發(fā)送訂閱數(shù)據(jù)時(shí)會(huì)先執(zhí)行此方法。

// RACSignal+Operations.m 源碼
- (RACSignal *)doNext:(void (^)(id x))block {
    NSCParameterAssert(block != NULL);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            // 先會(huì)掉block
            block(x);
            // 執(zhí)行訂閱發(fā)送
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -doNext:", self.name];
}

<b>注意:</b>doNext:方法返回的事一個(gè)新的signal巢株,這是一個(gè)新創(chuàng)建的信號(hào)槐瑞,并且在[subscriber sendNext:x]之前,首先回調(diào)了block(x)阁苞。所以困檩,如果將doNext:單獨(dú)拿出來(lái)帅刊,在之后的信號(hào)訂閱過(guò)程中八孝,是不會(huì)執(zhí)行doNext:方法。因?yàn)橛嗛喌氖亲钤嫉男盘?hào)牺荠。

看一下上面代碼的打印結(jié)果骚灸,如下:

RACDemo[44640:4871628] do next
RACDemo[44640:4871628] subscriber receive value a
RACDemo[44640:4871628] do Completed
RACDemo[44640:4871628] completed end

<b>RACSiganle先說(shuō)這么多糟趾,其他的會(huì)在下一篇文章中詳細(xì)解釋。</b>

RACSequence

單純的翻譯來(lái)看就是<b>序列</b>甚牲。那這個(gè)是用來(lái)干嘛的呢义郑?

Represents an immutable sequence of values. Unless otherwise specified, the sequences' values are evaluated lazily on demand. Like Cocoa collections, sequences cannot contain nil.

官方是這么解釋的,通俗的翻譯一下就是:
RACSequence代表的是一組不可變的序列值丈钙。除非另有說(shuō)明魔慷,這個(gè)序列值在使用的時(shí)候通過(guò)懶加載的方式。跟cocoa框架中的集合一樣著恩,不能包含空值。

想到這里,就會(huì)想到Foundation中的NSArray喉誊,NSDictionary邀摆。不錯(cuò),這里可以將NSArray伍茄,NSDictionary轉(zhuǎn)換為RACSequence栋盹。并且通過(guò)RACSequence轉(zhuǎn)為RACSignal

<b>NSArray轉(zhuǎn)換 --> RACArraySequence</b>

NSArray *array = @[@"a", @"b", @"c"];
// 可以調(diào)用array.rac_sequence直接做轉(zhuǎn)換
RACSequence *arraySeq = array.rac_sequence;
RACSignal *arraySignal = arraySeq.signal;
    
[arraySignal subscribeNext:^(id x) {
    NSLog(@"subscriber array receive value: %@",x);
}];

源碼

// NSArray+RACSequenceAdditions.m
- (RACSequence *)rac_sequence {
    return [RACArraySequence sequenceWithArray:self offset:0];
}

// RACArraySequence.m
+ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
    NSCParameterAssert(offset <= array.count);

    if (offset == array.count) return self.empty;

    RACArraySequence *seq = [[self alloc] init];
    seq->_backingArray = [array copy];
    seq->_offset = offset;
    return seq;
}

上述源碼中提到了RACArraySequence敷矫,調(diào)用了一個(gè)類(lèi)方法直接返回RACArraySequence例获。
這個(gè)RACArraySequence中有兩個(gè)屬性分別存儲(chǔ)數(shù)組和偏移。這個(gè)offset值得一提曹仗,代表的是這個(gè)Sequence是從array的第幾個(gè)開(kāi)始的榨汤。接下來(lái)就會(huì)看到。往下看

// 獲取頭
- (id)head {
    return self.backingArray[self.offset];
}

// 獲取尾部
- (RACSequence *)tail {
    RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1];
    sequence.name = self.name;
    return sequence;
}

// 獲取數(shù)組
- (NSArray *)array {
    return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}

這個(gè)head返回的值是數(shù)組中第offset個(gè)數(shù)據(jù)怎茫。返回的array是從第offset個(gè)開(kāi)始的收壕。

tail這個(gè)方法返回的還是一個(gè)序列,而且這個(gè)序列是從第offset+1個(gè)數(shù)據(jù)開(kāi)始的轨蛤,有可能回返回empty蜜宪。
以上的三個(gè)方法會(huì)在RACSequence中詳細(xì)介紹。

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;

這個(gè)方法是NSFastEnumeration協(xié)議中的方法祥山,不做顯式調(diào)用圃验。在這里就不去追究了。

<b>NSDictionary轉(zhuǎn)換</b>

NSDictionary *dict = @{@"key1" : @"value1",
                       @"key2" : @"value2"};
RACSequence *dictSeq = dict.rac_sequence;
RACSignal *dictSignal = dictSeq.signal;
    
// 字典中的數(shù)據(jù)流比較特殊缝呕、是一個(gè)`RACTuple`類(lèi)型澳窑,下面會(huì)講到。
[dictSignal subscribeNext:^(RACTuple *tupe) {
    NSLog(@"key = %@, value = %@", tupe.first, tupe.second);
    }];
// 所有的key的序列
RACSequence *keysSeq = dict.rac_keySequence;
// 所有的value的序列
RACSequence *valuesSeq = dict.rac_valueSequence;
- (RACSequence *)rac_sequence {
    NSDictionary *immutableDict = [self copy];

    // TODO: First class support for dictionary sequences.
    return [immutableDict.allKeys.rac_sequence map:^(id key) {
        id value = immutableDict[key];
        return RACTuplePack(key, value);
    }];
}

源碼中可以看到是通過(guò)獲取dictionary所有key來(lái)重新組成一個(gè)新的序列岳颇。主要的就是一個(gè)map函數(shù)照捡,不要著急,馬上就會(huì)介紹话侧。

RACSequence的方法

這個(gè)東西栗精,真的是很牛x的,感慨一下瞻鹏。源文件就不貼了悲立,自己看吧,一個(gè)一個(gè)介紹新博。

head薪夕、tail、array

// 返回第一個(gè)序列中的第一個(gè)數(shù)據(jù)
@property (nonatomic, strong, readonly) id head;

// 返回除了第一個(gè)數(shù)據(jù)之外的其他數(shù)據(jù)組成的序列
@property (nonatomic, strong, readonly) RACSequence *tail;

// 返回一個(gè)數(shù)組
@property (nonatomic, copy, readonly) NSArray *array;

這幾個(gè)屬性就不說(shuō)了赫悄,上面已經(jīng)介紹過(guò)了原献。

objectEnumerator

// RACSequence.h
@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;

// RACSequence.m
- (NSEnumerator *)objectEnumerator {
    RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
    enumerator.sequence = self;
    return enumerator;
}

這個(gè)objectEnumerator其實(shí)是一個(gè)RACSequenceEnumerator馏慨,是一個(gè)遍歷,來(lái)自NSEnumerator姑隅。

-eagerSequence写隶、-lazySequence

/// Converts a sequence into an eager sequence.
///
/// An eager sequence fully evaluates all of its values immediately. Sequences
/// derived from an eager sequence will also be eager.
///
/// Returns a new eager sequence, or the receiver if the sequence is already
/// eager.
@property (nonatomic, copy, readonly) RACSequence *eagerSequence;

/// Converts a sequence into a lazy sequence.
///
/// A lazy sequence evaluates its values on demand, as they are accessed.
/// Sequences derived from a lazy sequence will also be lazy.
///
/// Returns a new lazy sequence, or the receiver if the sequence is already lazy.
@property (nonatomic, copy, readonly) RACSequence *lazySequence;

這里需要詳細(xì)解釋一下。

在一開(kāi)始提到RACSequence的時(shí)候讲仰,我們就已經(jīng)說(shuō)過(guò):<b>除非另有說(shuō)明慕趴,這個(gè)序列值在使用的時(shí)候通過(guò)懶加載的方式。</b>而eagerSequence則是lazy的對(duì)立面鄙陡。

// 通過(guò)RACEagerSequence返回
- (RACSequence *)eagerSequence {
    return [RACEagerSequence sequenceWithArray:self.array offset:0];
}

// 返回自己
- (RACSequence *)lazySequence {
    return self;
}

我們先不看其原理是怎么樣的冕房,我們先來(lái)看個(gè)例子。原理在下一章會(huì)講趁矾。

NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
// 這里又用到了map函數(shù)(映射)耙册,馬上就會(huì)講到
RACSequence *seq1 = [array1.rac_sequence map:^id(id value) {
    NSLog(@"lazy value = %@", value);
    return value;
}];
    
RACSequence *seq2 = [array2.rac_sequence.eagerSequence map:^id(id value) {
    NSLog(@"eager value = %@", value);
    return value;
}];
    
NSArray *newArray1 = seq1.array;
NSArray *newArray2 = seq2.array;

// 打印結(jié)果
RACDemo[26685:1927997] eager value = 1
RACDemo[26685:1927997] eager value = 2
RACDemo[26685:1927997] eager value = 3
RACDemo[26685:1927997] lazy value = a
RACDemo[26685:1927997] lazy value = b
RACDemo[26685:1927997] lazy value = c

從打印結(jié)果來(lái)看,先打印的eager愈魏,然后才是lazy觅玻。在這里即使將后兩行代碼注釋掉,也會(huì)執(zhí)行eager里的NSLog培漏。先劇透一下原理溪厘,其實(shí)就是bind,大家可以先了解一下牌柄。

-signal畸悬、-signalWithScheduler:

- (RACSignal *)signal;

- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler;

RACSequence轉(zhuǎn)為RACSignal

RACScheduler在合適的時(shí)候會(huì)說(shuō)一下這個(gè)類(lèi)珊佣,內(nèi)部使用GCD實(shí)現(xiàn)的蹋宦。

-foldLeftWithStart:reduce:、-foldRightWithStart:reduce

- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce;
- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce;

這兩個(gè)方法都是遍歷咒锻、給一個(gè)開(kāi)始的值冷冗,一個(gè)是從左開(kāi)始遍歷,一個(gè)是從又開(kāi)始遍歷惑艇。簡(jiǎn)化方法就是:

[1,2,3] startValue

left: reduce(reduce(reduce(startValue,1),2),3)

right reduce(reduce(reduce(1,startValue),2),3)

舉個(gè)??吧蒿辙,看的更真切。

NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
    
id a = [array1.rac_sequence foldLeftWithStart:@"d" reduce:^id(id accumulator, id value) {
    NSLog(@"accumulator = %@, value = %@", accumulator, value);
    return [NSString stringWithFormat:@"%@ + %@",accumulator, value];
}];
NSLog(@"a = %@", a);
    
id b = [array2.rac_sequence foldRightWithStart:@(4) reduce:^id(id first, RACSequence *rest) {
    NSLog(@"first = %@, rest.head = %@",first, rest.head);
    return @([first integerValue] + [rest.head integerValue]);
}];
NSLog(@"b = %@", b);

// 打印結(jié)果:
RACDemo[27480:1956629] accumulator = d, value = a
RACDemo[27480:1956629] accumulator = d + a, value = b
RACDemo[27480:1956629] accumulator = d + a + b, value = c
RACDemo[27480:1956629] a = d + a + b + c
RACDemo[27480:1956629] first = 3, rest.head = 4
RACDemo[27480:1956629] first = 2, rest.head = 7
RACDemo[27480:1956629] first = 1, rest.head = 9
RACDemo[27480:1956629] b = 10

-any滨巴、-all

- (BOOL)any:(BOOL (^)(id value))block;
- (BOOL)all:(BOOL (^)(id value))block;

這兩個(gè)都是測(cè)試型函數(shù),比較簡(jiǎn)單思灌。

  1. any是序列中的任一一個(gè)元素滿(mǎn)足某個(gè)條件為true。
  2. all則是序列中的所有元素都滿(mǎn)足某一條件返回true恭取。內(nèi)部調(diào)用foldLeftWithStart:一個(gè)一個(gè)比較泰偿。這里感覺(jué)不太好,如果有一個(gè)不滿(mǎn)足就應(yīng)該直接返回BOOL值蜈垮,剩下的元素就不應(yīng)該再比較了耗跛。浪費(fèi)資源啊裕照。

-objectPassingTest:

這個(gè)函數(shù)是在any:函數(shù)中調(diào)用的。內(nèi)部調(diào)用了flatmap:课兄。滿(mǎn)足條件的記錄下來(lái)牍氛,不滿(mǎn)足條件的過(guò)濾掉,如果有多個(gè)數(shù)據(jù)滿(mǎn)足條件烟阐,返回第一個(gè)。

- (id)objectPassingTest:(BOOL (^)(id value))block;

+sequenceWithHeadBlock:tailBlock:

+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock;

這個(gè)就更好理解了紊扬,這是一個(gè)類(lèi)方法蜒茄,通過(guò)我們之前了解到的headtail大概也能知道,就是自己定義head和tail組合成一個(gè)新的RACSequence餐屎。

RACTupleNil

RACTuple.h中有一個(gè)RACTupleNil類(lèi)檀葛,這個(gè)類(lèi)就是為了表示一個(gè)null的元素。也就是說(shuō)RACTuple的元素中有null則用RACTupleNil表示腹缩。

RACTuple中屿聋,用RACTupleNil來(lái)表示元組中沒(méi)有內(nèi)容。

RACTupleNil是一個(gè)單例藏鹊,用RACTupleNil.tupleNil來(lái)表示润讥。
不知道設(shè)計(jì)這個(gè)類(lèi)的目的是什么?

RACTuple

這是一個(gè)遵循NSCoding, NSCopying, NSFastEnumeration協(xié)議的繼承自NSObject的有序的序列盘寡。

屬性

  1. count tuple中的元素個(gè)數(shù)
  2. frist tuple中第一個(gè)元素楚殿,second/third/fourth/fifth
  3. last 最后一個(gè)元素
    這幾個(gè)屬性是只讀,獲取當(dāng)前tuple中的第幾個(gè)元素竿痰。當(dāng)index>count時(shí)脆粥,返回nil。

RACTuple.m中影涉,我們看看RACTuple的具體實(shí)現(xiàn)

@interface RACTuple ()
@property (nonatomic, strong) NSArray *backingArray;
@end

這里有一個(gè)數(shù)組变隔,用來(lái)表示元組中的元素。是一個(gè)不可變的數(shù)組

#pragma mark NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
    // we're immutable, bitches!
    return self;
}

上面的源代碼中有一句作者的注釋?zhuān)f(shuō)這是一個(gè)不可變的蟹倾,也就是說(shuō)元組是不可變的匣缘。

+tupleWithObjectsFromArray:

將數(shù)組轉(zhuǎn)換成tuple,在實(shí)現(xiàn)中調(diào)用的是下面的方法喊式。

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
    return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}

+tupleWithObjectsFromArray:convertNullsToNils:

將數(shù)組轉(zhuǎn)換為tuple孵户,如果convertYES則將NSNUll轉(zhuǎn)換為RACTupleNil

RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]]];
NSLog(@"%@", tuple);
RACTuple *tuple1 = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]] convertNullsToNils:YES];
NSLog(@"%@", tuple1);

在這里我們發(fā)現(xiàn)這兩個(gè)輸出顯示的元組中的元素是一樣的。
ReactiveCocoaTest[40628:6604925] <RACTuple: 0x600000005ea0> (
    1,
    2,
    "<null>"
)
ReactiveCocoaTest[40628:6604925] <RACTuple: 0x608000005c00> (
    1,
    2,
    "<null>"
)

那這兩個(gè)到底有什么區(qū)別岔留?

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert {
    RACTuple *tuple = [[self alloc] init];
    
    if (convert) {
        NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
        for (id object in array) {
            [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];
        }
        
        tuple.backingArray = newArray;
    } else {
        tuple.backingArray = [array copy];
    }
    
    return tuple;
}

從上面的源碼中可以看到夏哭,當(dāng)convert == YES時(shí),會(huì)將NSNull.null轉(zhuǎn)為RACTupleNil.tupleNil献联。存儲(chǔ)的數(shù)據(jù)發(fā)生了變化竖配。在控制臺(tái)輸出時(shí)何址,會(huì)調(diào)用[class description]方法。

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects];
    //這里打印的是self.allObjects
}

allObjects是一個(gè)數(shù)組进胯,在其內(nèi)部又會(huì)將RACTupleNil.tupleNil轉(zhuǎn)換為NSNull.null用爪,所以控制臺(tái)的輸出結(jié)果是一樣的。

- (NSArray *)allObjects {
    NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count];
    for (id object in self.backingArray) {
        [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)];
    }
    
    return newArray;
}

-tupleWithObjects:

類(lèi)似于數(shù)組的初始化方法胁镐,將數(shù)據(jù)轉(zhuǎn)為tuple

-objectAtIndex:

獲取當(dāng)前index的元素偎血,如果index > count則返回nil。

-allObjects

獲取tuple中的所有元素盯漂,組成一個(gè)數(shù)組颇玷。

-tupleByAddingObject:

類(lèi)似于可變數(shù)組,將一個(gè)元素拼接在tuple的元素后面并返回一個(gè)新的tuple就缆。

RACTupleUnpackingTrampoline

在來(lái)說(shuō)說(shuō)這個(gè)類(lèi)帖渠,看到的時(shí)候是一臉懵逼,這個(gè)是干么的竭宰。連個(gè)注釋都沒(méi)有...
兩個(gè)方法空郊,一個(gè)是單例,就不說(shuō)了切揭,另一個(gè)看代碼

- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables {
    NSCParameterAssert(variables != nil);
    
    [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) {
        __strong id *ptr = (__strong id *)value.pointerValue;
        *ptr = tuple[index];
    }];
}

這個(gè)方法傳進(jìn)來(lái)兩個(gè)值狞甚,一個(gè)是tuple,一個(gè)是array(注意:數(shù)組中的元素是NSValue)
遍歷數(shù)組伴箩,取數(shù)組的每一個(gè)元素的指針入愧,然后將tuple對(duì)應(yīng)的值賦值給這個(gè)指針對(duì)應(yīng)的值。

NSString *str1 = @"1";
NSString *str2 = @"2";
NSString *str3 = @"3";
NSArray *array = @[[NSValue valueWithPointer:&str1], 
                   [NSValue valueWithPointer:&str2],
                   [NSValue valueWithPointer:&str3]];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);
    
RACTupleUnpackingTrampoline *tupleTramp = RACTupleUnpackingTrampoline.trampoline;
RACTuple *tuple = RACTuplePack(@"a", @"b", @"c");
[tupleTramp setObject:tuple forKeyedSubscript:array];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);

看看輸出的結(jié)果:

ReactiveCocoaTest[78079:10162531] str1 = 3, str2 = 4
ReactiveCocoaTest[78079:10162531] st1 = 3, st2 = 4

其實(shí)這樣的方法是沒(méi)有什么實(shí)際用處的嗤谚,也不會(huì)有人這么去寫(xiě)棺蛛。但是定義了這個(gè)類(lèi),就肯定有它的實(shí)際意義巩步。接下來(lái)我們看看RACTuple的兩個(gè)宏定義旁赊。

RACTuplePack、RACTupleUnpack

  1. RACTuplePack
    將數(shù)據(jù)包裝成一個(gè)RACTuple類(lèi)型椅野。至少有一個(gè)數(shù)據(jù)终畅。
/// Creates a new tuple with the given values. At least one value must be given.
/// Values can be nil.
#define RACTuplePack(...) \
    RACTuplePack_(__VA_ARGS__)

#define RACTuplePack_(...) \
    ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])
    
#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \
    (ARG) ?: RACTupleNil.tupleNil,

首先這個(gè)宏定義中使用了[RACTuple tupleWithObjectsFromArray:]這個(gè)函數(shù)來(lái)創(chuàng)建一個(gè)新的tuple。

RACTuple *tuple = RACTuplePack(@1, @2);
  1. RACTupleUnpack
///   RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
///
///   /* The above is equivalent to: */
///   RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil];
///   NSString *string = t[0];
///   NSNumber *num = t[1];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
#define RACTupleUnpack(...) \
        RACTupleUnpack_(__VA_ARGS__)

在源文件中已經(jīng)給出了例子了竟闪,就不在舉例子了离福。我們仔細(xì)看看這個(gè)宏是怎么搞的

#define RACTupleUnpack_(...) \
    metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \
    \
    int RACTupleUnpack_state = 0; \
    \
    RACTupleUnpack_after: \
        ; \
        metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \
        if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \
        \
        while (RACTupleUnpack_state != 2) \
            if (RACTupleUnpack_state == 1) { \
                goto RACTupleUnpack_after; \
            } else \
                for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \
                    [RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ]

一大堆的代碼,如果一級(jí)一級(jí)的去看這個(gè)宏中的內(nèi)容炼蛤,會(huì)發(fā)現(xiàn)很復(fù)雜妖爷,但是我們看到了最后一行代碼,上面提到的類(lèi)在這里是有用處的理朋。

我們寫(xiě)個(gè)例子絮识,對(duì)這個(gè)宏進(jìn)行編譯一下

RACTupleUnpack(NSString *st1, NSString *st2) = [RACTuple tupleWithObjects:@"3", @"4", nil];

這個(gè)代碼的形式跟Swift的元組很像绿聘。

let (intA, intB) = (1, 2)

接下來(lái),我們對(duì)這段代碼進(jìn)行編譯:

__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var1;
int RACTupleUnpack_state79 = 0; RACTupleUnpack_after79: ;
__attribute__((objc_ownership(strong))) NSString *st1 = RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) NSString *st2 = RACTupleUnpack79_var1;
    
if (RACTupleUnpack_state79 != 0)
    RACTupleUnpack_state79 = 2;
while (RACTupleUnpack_state79 != 2)
    if (RACTupleUnpack_state79 == 1) {
        goto RACTupleUnpack_after79;
    }
    else
        for (; RACTupleUnpack_state79 != 1; RACTupleUnpack_state79 = 1) 
            [RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack79_var0], [NSValue valueWithPointer:&RACTupleUnpack79_var1], ] ] = [RACTuple tupleWithObjects:@"3", @"4", ((void *)0)];

看到這一坨代碼次舌,到最后是不是感覺(jué)瞬間懂了熄攘。
通過(guò)[NSValue valueWithPointer:]獲取指針?lè)旁跀?shù)組中。然后通過(guò)賦值的方式將數(shù)據(jù)分別傳給兩個(gè)指針彼念。

總結(jié):聊一聊Push-Driven挪圾、Pull-Driven

RACStream.png

RACSignal和RACSequence都是繼承自RACStream。信號(hào)是Push-Driven的信號(hào)流国拇,序列是Pull-Driven的序列流洛史。

Push-driven means that values for the signal are not defined at the moment of signal creation and may become available at a later time (for example, as a result from network request, or any user input).

Push-driven : 在創(chuàng)建信號(hào)的時(shí)候,信號(hào)不會(huì)被立即賦值酱吝,之后才會(huì)被賦值(舉個(gè)栗子:網(wǎng)絡(luò)請(qǐng)求回來(lái)的結(jié)果或者是任意的用戶(hù)輸入的結(jié)果)。

Pull-driven means that values in the sequence are defined at the moment of signal creation and we can query values from the stream one-by-one.

Pull-driven : 在創(chuàng)建信號(hào)的同時(shí)序列中的值就會(huì)被確定下來(lái)土思,我們可以從流中一個(gè)個(gè)地查詢(xún)值务热。

RACSequence <=> RACSignal

RACSequence也可以轉(zhuǎn)換為RACSignal,轉(zhuǎn)換時(shí)需要把sequence里面的值放到一個(gè)RACScgeduler中己儒,按照順序把值全部push到新創(chuàng)建的RACSignal中崎岂。

RACSignal也可以轉(zhuǎn)換為RACSequence,轉(zhuǎn)換時(shí)需要收集所有發(fā)送到這個(gè)signal的值闪湾,直到complete完成冲甘。會(huì)用到[signal toArray]這個(gè)方法,這個(gè)方法會(huì)阻塞當(dāng)前線(xiàn)程途样。

RACSequence:是Pull-Driven江醇。除了特別聲明(eager)都是懶加載。在創(chuàng)建時(shí)何暇,序列中的值就已經(jīng)確定陶夜。當(dāng)被訂閱時(shí)會(huì)直接返還給訂閱者。當(dāng)轉(zhuǎn)換為signal后裆站,會(huì)將數(shù)據(jù)按照順序全部push到信號(hào)条辟。

RACSignal:是Push-Driven。signal是有訂閱者的存在才有意義(一般的信號(hào)都是冷信號(hào)宏胯,被創(chuàng)建之后羽嫡,只有訂閱才會(huì)被激活)。signal發(fā)送的不僅僅是數(shù)據(jù)肩袍,還包括狀態(tài)(error杭棵,complete)。

下一節(jié)來(lái)重點(diǎn)說(shuō)說(shuō)bind:等了牛。

參考鏈接:

iOS的函數(shù)響應(yīng)型編程

ReactiveCocoa模式--Signal

Reactive Cocoa Tutorial [3] = RACSignal的巧克力工廠

謝謝大家颜屠,有寫(xiě)的不對(duì)的地方辰妙,歡迎指正,共同進(jìn)步甫窟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末密浑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子粗井,更是在濱河造成了極大的恐慌尔破,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇衬,死亡現(xiàn)場(chǎng)離奇詭異懒构,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)耘擂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)胆剧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人醉冤,你說(shuō)我怎么就攤上這事秩霍。” “怎么了蚁阳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵铃绒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我螺捐,道長(zhǎng)颠悬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任定血,我火速辦了婚禮赔癌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糠悼。我一直安慰自己届榄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布倔喂。 她就那樣靜靜地躺著铝条,像睡著了一般。 火紅的嫁衣襯著肌膚如雪席噩。 梳的紋絲不亂的頭發(fā)上班缰,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音悼枢,去河邊找鬼埠忘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莹妒。 我是一名探鬼主播名船,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旨怠!你這毒婦竟也來(lái)了渠驼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鉴腻,失蹤者是張志新(化名)和其女友劉穎迷扇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體爽哎,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜓席,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了课锌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厨内。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渺贤,靈堂內(nèi)的尸體忽然破棺而出隘庄,到底是詐尸還是另有隱情,我是刑警寧澤癣亚,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站获印,受9級(jí)特大地震影響述雾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兼丰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一玻孟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳍征,春花似錦黍翎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至氮双,卻和暖如春碰酝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戴差。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工送爸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓袭厂,卻偏偏與公主長(zhǎng)得像墨吓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纹磺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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