RAC 之 RACSignal 使用與源碼分析

RACSignal 使用

   // 1 創(chuàng)建信號
    @weakify(self)
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {

        @strongify(self)
        self.subsrcribe = subscriber;

        // 3 發(fā)送信號
        [subscriber sendNext:@"hahah"];

        return [RACDisposable disposableWithBlock:^{
            NSLog(@"銷毀了");
        }];
    }];

    // 2 訂閱信號
    [signal subscribeNext:^(id  _Nullable x) {
        // 第 3 步 發(fā)送信號后舶担, 此處 block 會被回調(diào)
        NSLog(@"subscribeNext:%@",x);
    }];

根據(jù)上述代碼流程想暗,RACSignal 的使用分別有 3 步

  • 創(chuàng)建:實例一個 RACSignal對象速蕊,并實現(xiàn)一個能夠獲取 subscriber 訂閱者的 block
  • 訂閱:使用RACSignal對象進行訂閱滓侍,用于獲取發(fā)送信號后的回調(diào)結(jié)果
  • 發(fā)送信號:使用subscriber 訂閱者在特定位置發(fā)送信號(發(fā)送結(jié)果)顶伞,訂閱信號的 block 獲取結(jié)果

一、RACSignal 的創(chuàng)建

以下我們開始分析一下 RACSignal類型的對象是怎么來的

    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {

        return [RACDisposable disposableWithBlock:^{
            
        }];
    }];

上述RACSignal 的創(chuàng)建 方法,

//  RACSignal.h
+ (RACSignal<ValueType> *)createSignal:
(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe
//  RACSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
     //RACDynamicSignal 繼承于 RACSignal
    return [RACDynamicSignal createSignal:didSubscribe];
}

因為返回的是 RACSignal類型势誊,卻使用了

return [RACDynamicSignal createSignal:didSubscribe];

由此可得 RACDynamicSignal 這個子類做了一系列的實例化和包裝處理呜达,如下:

//RACDynamicSignal.m

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
        //  setNameWithFormat: 進行了一些列的底層打印日志操作,
       //   最后還是返回了一個 RACSignal 的對象
    return [signal setNameWithFormat:@"+createSignal:"];
}

二粟耻、RACSignal 訂閱信號

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"subscribeNext:%@",x);
    }];

上述方法是在 RACSignal (Subscription)類別里的一個方法:

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    // 創(chuàng)建 subscribe 對象 并保存 Next查近、Error、Completed 三個Block
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    // 傳入 subscribe 并創(chuàng)建 disposable
    return [self subscribe:o];
}

RACSubscriber的對象主要保存了 Next挤忙、Error霜威、Completed 3個Block,再往里面跟一層如下:

// RACSubscriber.m
+ (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;
}

然后回到 上面的類別: RACSignal (Subscription)

  return [self subscribe:o];

上述方法雖然是在本類別里調(diào)用

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCAssert(NO, @"This method must be overridden by subclasses");
    return nil;
}

但卻被子類 RACDynamicSignal 重寫了

// RACDynamicSignal.m
#pragma mark Managing Subscribers
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    // 傳入 subscribe 并創(chuàng)建 disposable册烈,
    // 復(fù)合銷毀者
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    
    /**
     *  subscriber --- RACSubscriber
     *  signal     --- self 
     *  disposable
     */

    //  新的(RACPassthroughSubscriber*)subscriber 擁有:disposable戈泼、signal、subscribe 三者合成
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    // signal 有執(zhí)行 didsubscribe Block 往下走
    if (self.didSubscribe != NULL) {
        
        //  這里需要注意G牙濉0L竿稹次哈!  如果開啟了異步操作,RACScheduler.subscriptionScheduler 的類型是 “RACTargetQueueScheduler” 并在子線程執(zhí)行 【RACTargetQueueScheduler schedule:】
        
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            // 這一步 self.didSubscribe(subscriber) 進行block 回調(diào)出去
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            // 添加銷毀
            [disposable addDisposable:innerDisposable];
        }];

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

上述代碼我們主要看訂閱者subscriber 的回調(diào)流程:

// 只要外部執(zhí)行了
[RACSignal  createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
  // 這里得到 subscriber 的底層回調(diào)
}

 當(dāng)?shù)讓邮褂?self.didSubscribe(subscribe) 就會回調(diào)出去上面的代碼塊里

小結(jié)一下整個訂閱過程主要做了的就是:
signal 訂閱了 subscribeNext:^(id _Nullable x) 的 block吆录,并內(nèi)部配置了 subscribe窑滞,即:

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

此時 subscriber擁有了下面 signal的block 實現(xiàn)

// 三種訂閱原理都是一樣
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"subscribeNext:%@",x);
     }];

    [signal subscribeError:^(NSError * _Nullable error) {
        NSLog(@"subscribeError");
    }];

    [signal subscribeCompleted:^{
        NSLog(@"subscribeCompleted");
    }];

下一步subscriber 就可以根據(jù)不同場景發(fā)送信號進行 block 回調(diào)了

三、 RACSignal 發(fā)送信號

使用subscriber發(fā)送信號:

        [subscriber sendNext:@"hahah"];
    
        [subscriber sendError:nil];

        [subscriber sendCompleted];
// RACSubscriber.m
#pragma mark RACSubscriber

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

- (void)sendError:(NSError *)e {
    @synchronized (self) {
        void (^errorBlock)(NSError *) = [self.error copy];
        // 在回調(diào) block 之前 ,執(zhí)行了銷毀方法
        [self.disposable dispose];

        if (errorBlock == nil) return;
        errorBlock(e);
    }
}

- (void)sendCompleted {
    @synchronized (self) {
        void (^completedBlock)(void) = [self.completed copy];
        // 在回調(diào) block 之前 哀卫,執(zhí)行了銷毀方法
        [self.disposable dispose];

        if (completedBlock == nil) return;
        completedBlock();
    }
}

上面 subscriber 根據(jù)不同方法分別調(diào)用了 nextBlock(value)巨坊,errorBlock(e),completedBlock() 使得第二步 signal的訂閱信號得到了回調(diào)結(jié)果此改;

值得注意的是:

- (void)sendError:(NSError *)e
- (void)sendCompleted;

這兩個方法的實現(xiàn)里趾撵,分別都調(diào)用了

[self.disposable dispose];

這部操作把 subscriber進行了銷毀, 之后的subscriber如果再調(diào)用 sendNext, sendError , sendCompleted 或其他發(fā)送信號的方法共啃,都已經(jīng)失效了占调。

關(guān)于 RACSignal,RACSubscriber 就暫時介紹到這里,上述涉及到RACDisposable,RACScheduler 沒有作出過多的解釋移剪,后續(xù)抽時間會繼續(xù)寫下去究珊。

以上內(nèi)容純粹個人見解,僅用于分享交流纵苛;如有描述不當(dāng)之處剿涮,歡迎指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末攻人,一起剝皮案震驚了整個濱河市取试,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贝椿,老刑警劉巖想括,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烙博,居然都是意外死亡瑟蜈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門渣窜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铺根,“玉大人,你說我怎么就攤上這事乔宿∥挥兀” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵详瑞,是天一觀的道長掂林。 經(jīng)常有香客問我,道長坝橡,這世上最難降的妖魔是什么泻帮? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮计寇,結(jié)果婚禮上锣杂,老公的妹妹穿的比我還像新娘脂倦。我一直安慰自己,他們只是感情好元莫,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布赖阻。 她就那樣靜靜地躺著,像睡著了一般踱蠢。 火紅的嫁衣襯著肌膚如雪火欧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天茎截,我揣著相機與錄音布隔,去河邊找鬼。 笑死稼虎,一個胖子當(dāng)著我的面吹牛衅檀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霎俩,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼哀军,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了打却?” 一聲冷哼從身側(cè)響起杉适,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柳击,沒想到半個月后猿推,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡捌肴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年蹬叭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片状知。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡秽五,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饥悴,到底是詐尸還是另有隱情坦喘,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布西设,位于F島的核電站瓣铣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贷揽。R本人自食惡果不足惜棠笑,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擒滑。 院中可真熱鬧腐晾,春花似錦、人聲如沸丐一。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽库车。三九已至巨柒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柠衍,已是汗流浹背洋满。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珍坊,地道東北人牺勾。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像阵漏,于是被迫代替她去往敵國和親驻民。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359