RACSignal的子類們

研究和使用RAC有一段時間了蓄诽,但一直很懶就漾,不想寫任何東西粱坤。決定還是寫一點東西吧,也算是強迫自己整理一下冈在。計劃從最常用的也是RAC的核心(信號) RACSignal 這個類說起,來引出RAC的方方面面:冷熱信號實現(xiàn)及轉(zhuǎn)換按摘、RAC中FRP的體現(xiàn)包券、rac中的類和協(xié)議(RACDisposable纫谅、RACSubscriber、RACCommand溅固、 RACChannel...)付秕、 對信號的處理和控制(combineLatest、bind...)侍郭、ReactiveCocoa的設(shè)計思想及結(jié)構(gòu)體系询吴、遇到的一些坑...

RACSignal是什么?

在RAC中RACSignal是一個工廠類亮元,他通過各個工廠方法生成不同的子類實例猛计。同時他繼承基類RACStream(RAC的抽象類,只是定義了處理信號流的方法爆捞,子類通過繼承來具體實現(xiàn))奉瘤。

下面具體討論RACSignal的幾個子類


1. RACDynamicSignal

用法:

//1. 創(chuàng)建信號
 RACSignal *dynamicSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"send next"];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@" to release some object because signal had completed or error");
        }];
    }];
//2. 訂閱信號
 [dynamicSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

上面的代碼創(chuàng)建了一個signal, 如果需要dynamicSignal信號工作煮甥,我們需要對其訂閱盗温;訂閱之后subscriber就會將"send next"傳遞到x中,因此NSLog的打印就是"send next",如果你使用過RAC一定對此不會陌生成肘。 平時我們所指的signal其實就是RACDynamicSignal(通過createSignal工廠方法創(chuàng)建的一個子類)卖局。還有一些特殊用途的signal,例如RACErrorSignal双霍、RACEmptySignal吼驶、RACReturnSignal都是通過工廠方法獲得其實例。

下面通過源碼逐步解析:
1 . 創(chuàng)建信號

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    // 1店煞、創(chuàng)建RACDynamicSignal實例:signal
 RACDynamicSignal *signal = [[self alloc] init];
    // 2蟹演、signal 中保存 傳入的didSubscribe 以便后續(xù)對其調(diào)用
 signal->_didSubscribe = [didSubscribe copy];
    //tip: rac在創(chuàng)建不同實例的時候都會調(diào)用這個方法,給name屬性賦值(RACStream中唯一具體實現(xiàn)的方法)顷蟀,給不同對象不同的標(biāo)記用于輸出日志等
 return [signal setNameWithFormat:@"+createSignal:"];
}

2. 訂閱信號

// subscribeNext方法是對訂閱過程的一個封裝酒请,便于上層調(diào)用
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    //1、創(chuàng)建訂閱者鸣个,將nextBlock參數(shù)再次傳遞
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
       //2羞反、傳入訂閱者,進(jìn)行訂閱
    return [self subscribe:o];
}

2.1創(chuàng)建訂閱者

 // 這里是對訂閱者的初步創(chuàng)建囤萤,后面還有對它的二次處理
 + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
   //1昼窗、創(chuàng)建RACSubscriber實例:subscriber
 RACSubscriber *subscriber = [[self alloc] init];
     //2、分別記錄傳入的next涛舍、error澄惊、completed
 subscriber->_next = [next copy];
 subscriber->_error = [error copy];
 subscriber->_completed = [completed copy];

 return subscriber;
}

2.2訂閱

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
  /*
   * subscriber 被賦給了一個新的RACPassthroughSubscriber實例
   * 并將原來的subscriber以及signal本身和disposable作為新的subscriber的實例對象保存
   */
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    if (self.didSubscribe != NULL) {
          //下列方法會執(zhí)行傳入的bock參數(shù)(因涉及到RACDisposable和RACScheduler兩大塊,不便具體解釋過程,可以自己看源碼)    
            RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
           // 這里對《1.創(chuàng)建信號》中保存的didSubscribe進(jìn)行調(diào)用掸驱,并將subscriber作為block參數(shù)傳入
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}

思路整理:首先createSignal方法創(chuàng)建了一個信號dynamicSignal肛搬,并把傳入的block參數(shù)記錄在了dynamicSignal的didSubscribe中;然后subscribeNext方法中創(chuàng)建了一個訂閱者 o 用其屬性next保存了從subscribeNext方法傳入的block毕贼,接下來subscribeNext中調(diào)用subscribe方法并將 o 作為參數(shù)傳入温赔;而在subscribe方法中執(zhí)行了 self.didSubscribe(subscriber) 其中subscriber就是傳入subscribe方法的o。也就是執(zhí)行了一開始保存在dynamicSignal中的didSubscribe鬼癣。
也就是執(zhí)行了createSignal方法中傳入的block

 ^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"send next"];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@" to release some object because signal had completed or error");
        }];
}

終于到了最后一步陶贼,我們只需要弄清[subscriber sendNext:@"send next"];這一方法的具體實現(xiàn),整個RACDynamicSignal的工作原理就全部揭曉待秃。(sendCompleted 以及 RACDisposable 相關(guān)計劃下次介紹)
sendNext的實現(xiàn)如下:

- (void)sendNext:(id)value {
    @synchronized (self) {
        void (^nextBlock)(id) = [self.next copy];
        if (nextBlock == nil) return;

        nextBlock(value);
    }
}

可以看到這里其實就是對上文保存在 o 中的 next 進(jìn)行執(zhí)行拜秧,同時將傳入的 value 再次當(dāng)作 next 的參數(shù)傳入。也就是執(zhí)行了訂閱信號方法subscribeNext 時傳入的 block 锥余,如下

^(id x) {
        NSLog(@"%@",x);
    }

直到這里腹纳,整個信號的 “創(chuàng)建-發(fā)送-訂閱” 的過程全部分析完畢。
這里強調(diào)一下:RACDynamicSignal 中的信號需要被訂閱驱犹,然后信號才會被激活嘲恍。


2. RACSubject

RACSubject 是 RACSignal 的一個子類,用法如下:

    //1雄驹、創(chuàng)建實例
    RACSubject *subject = [RACSubject subject];
    //2佃牛、訂閱信號
    [subject subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    //3、發(fā)送信號
    [subject sendNext:@"send next"];

和RACSignal不同的是 RACSubject 是先訂閱信號医舆,再發(fā)送信號俘侠。RACSignal是冷信號,而RACSubject及其子類是熱信號蔬将,這也是為什么RACSubject的創(chuàng)建不是通過RACSignal的工廠方法而是單獨拿出來做為一個體系的原因爷速。下面通過源碼具體分析:
1 . 創(chuàng)建信號

//僅僅只是實例了一個對象
+ (instancetype)subject {
    return [[self alloc] init];
}

2. 訂閱信號

//執(zhí)行的還是其父類RACSignal中的方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

2.1 創(chuàng)建訂閱者

// 這里和RACSignal中也是一樣,執(zhí)行的RACSubscriber中的方法
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];

    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}

2.2 訂閱

// 這里開始不同霞怀,RACSubject對其進(jìn)行了重寫惫东。其實這個方法,在RACSignal中沒有實現(xiàn)毙石,RACDynamicSignal也是執(zhí)行的RACDynamicSignal中重寫的方法
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
      // 這里和RACDynamicSignal中不同廉沮,RACSubject持有一個數(shù)組subscribers,來儲存所有的訂閱者徐矩,對subject的每一次訂閱都會將訂閱者加入該數(shù)組保存起來
    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }
    
    return [RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            // Since newer subscribers are generally shorter-lived, search
            // starting from the end of the list.
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }];
}

3. 發(fā)送信號

// 循環(huán)遍歷subscribers數(shù)組滞时,逐個執(zhí)行:[subscriber sendNext:value]
- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id<RACSubscriber> subscriber in subscribers) {
        block(subscriber);
    }
}

和RACSignal不同的是:RACSubject 通過subscribeNext方法將訂閱者保存在subscribers數(shù)組中,供sendNext方法調(diào)用滤灯;而RACSignal是先保存一個didSubscribe對象坪稽,然后通過subscribeNext方法創(chuàng)建一個訂閱者subscriber作為參數(shù)傳入didSubscribe中曼玩,進(jìn)而subscriber才能發(fā)送信號。
簡言之:RACSubject會保存所有subscriber(狀態(tài))刽漂,sendNext只是對其操作演训。RACSignal 在被訂閱后才會生成subscriber弟孟,并立即對其操作贝咙。
這也是冷信號和熱信號的區(qū)別(冷信號需要通過訂閱來激活)

3. RACReplaySubject

RACReplaySubject 是 RACSubject 的一個子類,他們都是熱信號拂募,那么他們有什么不同呢庭猩,還是從源碼入手

 //1、創(chuàng)建實例
    RACReplaySubject *subject = [RACReplaySubject replaySubjectWithCapacity:2];
    //2陈症、訂閱信號
    [subject subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    //3蔼水、發(fā)送信號
    [subject sendNext:@"send next"];

1 . 創(chuàng)建信號

+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
    return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    if (self == nil) return nil;
    // 這里會用一個屬性capacity 將 傳入的數(shù)記錄下來,下面會有這個數(shù)的用途
    _capacity = capacity;
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);
    
    return self;
}
// 也可以直接對其實例录肯,capacity為最大無符號整數(shù)
- (instancetype)init {
        // const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;
    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}

2. 訂閱信號

// 也是在subscribe方法發(fā)生變化趴腋,走的是RACReplaySubject中的方法
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            // 這里會從valuesReceived里sendNext未被執(zhí)行過value,第三步中會有 valuesReceived 的操作
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;
                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}

3. 發(fā)送信號

- (void)sendNext:(id)value {
    @synchronized (self) {
                // 每次將發(fā)送過的value添加到valuesReceived中記錄下來
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
               // 調(diào)用父類也就是RACSubject的sendNext
        [super sendNext:value];
           // 通過初始化時的capacity值截取valuesReceived的元素(從數(shù)組頭部開始论咏,也就是最先添加進(jìn)來的value)
        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
    }
}

為了解釋發(fā)生了什么优炬,進(jìn)行如下測試

    RACReplaySubject *subject = [RACReplaySubject subject];
    [subject subscribeNext:^(id x) {
        NSLog(@"%@--1",x);
    }];
    [subject subscribeNext:^(id x) {
        NSLog(@"%@--2",x);
    }];
    [subject sendNext:@"send next1"];
    [subject sendNext:@"send next2"];
    [subject subscribeNext:^(id x) {
        NSLog(@"%@--3",x);
    }];

日志:

//1
2017-03-23 22:03:03.722 testdemo[3509:143199] send next1--1
//2
2017-03-23 22:03:03.722 testdemo[3509:143199] send next1--2
//3
2017-03-23 22:03:03.722 testdemo[3509:143199] send next2--1
//4
2017-03-23 22:03:03.723 testdemo[3509:143199] send next2--2
//5
2017-03-23 22:03:03.723 testdemo[3509:143199] send next1--3
//6
2017-03-23 22:03:03.723 testdemo[3509:143199] send next2--3

分析:

  1. [subject sendNext:@"send next1"] 毫無疑問會打印 1 2 兩條日志,同時將value存入了valuesReceived厅贪;
  2. [subject sendNext:@"send next2"] 會打印 3 4 兩條日志蠢护,也同時將value存入了valuesReceived,到此valuesReceived里就存有兩個value了养涮;
  3. 對subject 進(jìn)行第三次 subscribeNext 時葵硕,因為valuesReceived里是有值的,所以會執(zhí)行兩次subscriber sendNext,也就是打印出日志 5 和 6 贯吓。

RACReplaySubject 會將每次sendNext的值記錄到valuesReceived(根據(jù)capacity大小會移除最先添加的元素)懈凹,之后對RACReplaySubject的訂閱會將valuesReceived里的記錄通過訂閱者來發(fā)送。** 這也就是RACReplaySubject的區(qū)別**


對于RACSignal的介紹到此結(jié)束悄谐,我們可以發(fā)現(xiàn)對RACDynamicSignal的每次訂閱都會重復(fù)執(zhí)行didSubscribe介评,而RACSubject則不會。

在我們平時使用時尊沸,很多場景是需要用到 RACDynamicSignal 的威沫。
例如:我們會把數(shù)據(jù)處理、網(wǎng)絡(luò)請求洼专、UI刷新等等一系列操作放在didSubscribe里(這也是RAC的宗旨啊棒掠,將平時的各種操作都轉(zhuǎn)化為炫酷的信號的形式,優(yōu)雅的進(jìn)行傳遞與交互)屁商。但是我們不希望每次對RACSignal的訂閱都重復(fù)執(zhí)行didSubscribe烟很,因為這樣會帶來bug或者性能問題颈墅。

這時,我們要做的就是將冷信號轉(zhuǎn)換為熱信號雾袱,因為熱信號的重復(fù)訂閱是不會帶來這個問題的恤筛。那么如何將冷信號轉(zhuǎn)化為熱信號呢,后面再說吧芹橡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毒坛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子林说,更是在濱河造成了極大的恐慌煎殷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腿箩,死亡現(xiàn)場離奇詭異豪直,居然都是意外死亡,警方通過查閱死者的電腦和手機珠移,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門弓乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钧惧,你說我怎么就攤上這事暇韧。” “怎么了垢乙?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵锨咙,是天一觀的道長。 經(jīng)常有香客問我追逮,道長酪刀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任钮孵,我火速辦了婚禮骂倘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巴席。我一直安慰自己历涝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布漾唉。 她就那樣靜靜地躺著荧库,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赵刑。 梳的紋絲不亂的頭發(fā)上分衫,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音般此,去河邊找鬼蚪战。 笑死牵现,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邀桑。 我是一名探鬼主播瞎疼,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壁畸!你這毒婦竟也來了贼急?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瓤摧,失蹤者是張志新(化名)和其女友劉穎竿裂,沒想到半個月后玉吁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體照弥,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年进副,在試婚紗的時候發(fā)現(xiàn)自己被綠了这揣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡影斑,死狀恐怖给赞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情矫户,我是刑警寧澤片迅,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站皆辽,受9級特大地震影響柑蛇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驱闷,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一耻台、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧空另,春花似錦盆耽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至循榆,卻和暖如春析恢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冯痢。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工氮昧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留框杜,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓袖肥,卻偏偏與公主長得像咪辱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子椎组,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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