ReactiveCocoa學(xué)習(xí)筆記七-RACSubscriber源碼分析

Subscriber

到了最后一個(gè)部分了--訂閱者. 其實(shí)在Signal篇我們已經(jīng)接觸過了一些Subscriber, 畢竟Subject也是一個(gè)Subscriber.

而與RACSignal和RACDisposable不同的是, 與Subscriber相關(guān)的其它類不是用的繼承的形式, 而是組合(也就是其它類持有了一個(gè)RACSubscriber實(shí)例, 例如RACPassthroughSubscriber).

RACSubscriber

RACSubcriber是一般訂閱者的類, 整體而言代碼也是比較簡潔的, 首先要說明的是RACSubscriber有一個(gè)protocol的聲明, 也有一個(gè)類的聲明, 對于一個(gè)協(xié)議來說, 它Required了以下幾個(gè)方法:

- (void)sendNext:(id)value;

- (void)sendError:(NSError *)error;

- (void)sendCompleted;

- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

前面3個(gè)基本都見過了, 最后一個(gè)在Subject也見過一次. RACSubscriber類有個(gè)類方法返回實(shí)例, 但是頭文件標(biāo)注為私有的, 也就是說一般我們使用的時(shí)候不要關(guān)注它:

+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed;

再看看私有變量:

@interface RACSubscriber ()

@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
// 因?yàn)閰f(xié)議中有了addDisposable, 所以理所當(dāng)然要有個(gè)RACCompoundDisposable實(shí)例
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

@end

再看看幾個(gè)重要方法:

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    @unsafeify(self);

    // 首先要添加一個(gè)釋放自己資源的disposable
    RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{
        @strongify(self);

        @synchronized (self) {
            self.next = nil;
            self.error = nil;
            self.completed = nil;
        }
    }];

    _disposable = [RACCompoundDisposable compoundDisposable];
    [_disposable addDisposable:selfDisposable];

    return self;
}

- (void)dealloc {
    [self.disposable dispose];
}

值得一提的是, 雖然與Subscriber與Subject的作者都是同一人, 但是2者創(chuàng)建的時(shí)間卻是一個(gè)1月一個(gè)9月, 所以在寫的時(shí)候還是會有所區(qū)別, 特別是@unsafeify和@weakify, 之前看到一篇博文應(yīng)該是王巍寫的, 專門講RAC里面宏的精要, 還是很值得一看的.

再看看3個(gè)發(fā)送事件的方法:

- (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];
        [self.disposable dispose];

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

- (void)sendCompleted {
    @synchronized (self) {
        void (^completedBlock)(void) = [self.completed copy];
        [self.disposable dispose];

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

可以看到前期作者在寫的時(shí)候非常謹(jǐn)小慎微, 各種同步加持, 還用臨時(shí)變量來保證線程安全, 到了Subject的時(shí)候就會更加"隨意"一些.

同樣的, 最后這個(gè)didSubscribeWithDisposable也是如此, 這里以這個(gè)方法來看看Subject里面和Subscriber里面的實(shí)現(xiàn)差異

// RACSubscriber.m
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable {
    if (otherDisposable.disposed) return;

    RACCompoundDisposable *selfDisposable = self.disposable;
    [selfDisposable addDisposable:otherDisposable];

    @unsafeify(otherDisposable);

    [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
        @strongify(otherDisposable);
        [selfDisposable removeDisposable:otherDisposable];
    }]];
}
// RACSubject.m
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d {
    if (d.disposed) return;
    [self.disposable addDisposable:d];

    @weakify(self, d);
    [d addDisposable:[RACDisposable disposableWithBlock:^{
        @strongify(self, d);
        [self.disposable removeDisposable:d];
    }]];
}

發(fā)現(xiàn)去掉臨時(shí)變量之后, 二者基本上是一致的.

RACPassthroughSubscriber

在我們訂閱的時(shí)候, 實(shí)際創(chuàng)建的實(shí)例是RACPassthroughSubscriber, 但是一般我們不直接創(chuàng)建它, 都是RAC本身的實(shí)現(xiàn).

先看看頭文件:

@interface RACPassthroughSubscriber : NSObject <RACSubscriber>

- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;

實(shí)現(xiàn)了RACSubscriber協(xié)議, 并且signal, subcriber, disposable一應(yīng)俱全, 看起來這個(gè)類會維系好三者之間的關(guān)系.

看實(shí)現(xiàn)文件時(shí), 發(fā)現(xiàn)里面又來了一些 DTrace 的東西, 這個(gè)東西在 objc.io 里面有過介紹, Xcode 的Instruments就是基于此實(shí)現(xiàn), 有興趣的可以去看看. 從這個(gè)側(cè)面也應(yīng)該看的出來, RAC 團(tuán)隊(duì)對框架的性能是有過比較深度的評測的, 因此還是值得信賴的.

同時(shí)需要提一下的是, DTrace 的屬性沒有標(biāo)注為weak 而是unsafe_unretained, 眾所周知, weak 的屬性會在指向的對象被釋放后自動(dòng)置為nil, 因此要實(shí)現(xiàn)這一點(diǎn), 肯定就會在目標(biāo)對象上有存儲 weak 引用的列表, 據(jù)測為了保證 weak 引用, 中間產(chǎn)生的開銷還是不小的, 所以 RAC 會只在 DTrace 中用的屬性用unsafe_unretained, 以免造成不必要的開銷. 當(dāng)然, 使用了unsafe_unretained的屬性, 就不能輕易去 get 和 set 否則很容易造成野指針 crash.

實(shí)例初始化代碼:

- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
    NSCParameterAssert(subscriber != nil);

    self = [super init];
    if (self == nil) return nil;

    _innerSubscriber = subscriber;
    _signal = signal;
    _disposable = disposable;

    // 添加傳入的 disposable, 在調(diào)用 dispose 的時(shí)候一并清除掉
    [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
    return self;
}

RACSubscriber協(xié)議的其它方法就基本上沒有太多可講的了, 與上面的基本上是大同小異. 那么這里會引出一個(gè)問題, 為什么不直接用 RACSubscriber, 而要再引入一個(gè) passThrough 版本呢?

根據(jù)RACPassthroughSubscriber頭文件中對這個(gè)類的描述:

A private subscriber that passes through all events to another subscriber
while not disposed.

大意是說: 在未被清除的情況下, 把所有事件都傳遞到另一個(gè)訂閱者的私有訂閱者.

前提很好理解, 清除之后自然沒有必要再傳遞, 另一個(gè)訂閱者自然也就是傳入進(jìn)來的這個(gè)訂閱者, 也就是實(shí)際上需要發(fā)送時(shí)間的 RACSubscriber. 然而這段話并沒有解釋出來為什么需要這么樣個(gè)私有訂閱者呢, 從代碼角度上來看也就是加了 DTrace 的功能而已, 并沒有什么特殊的管理三者關(guān)系的代碼存在.

目前除了 DTrace 的探針之外, 我個(gè)人找不到很好的解釋, 發(fā)送郵件給作者想要問這個(gè)問題也暫時(shí)沒有得到回復(fù), 如果有回復(fù)我會更新上來. 除此之外, 我還特地把RACDynamicSignal里面的subscribe中把普通 RACSubscriber 變?yōu)镽ACPassThroughSubscriber 的代碼給注釋掉:

// subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

發(fā)現(xiàn)對我的 demo 應(yīng)用并沒有任何干擾, 因此也算一個(gè)佐證證明RACPassThroughSubscriber的作用的猜想了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子窘面,更是在濱河造成了極大的恐慌策州,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涣仿,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)发绢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門硬耍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人边酒,你說我怎么就攤上這事经柴。” “怎么了墩朦?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵坯认,是天一觀的道長。 經(jīng)常有香客問我氓涣,道長牛哺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任春哨,我火速辦了婚禮荆隘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赴背。我一直安慰自己椰拒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布凰荚。 她就那樣靜靜地躺著燃观,像睡著了一般。 火紅的嫁衣襯著肌膚如雪便瑟。 梳的紋絲不亂的頭發(fā)上缆毁,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音到涂,去河邊找鬼脊框。 笑死,一個(gè)胖子當(dāng)著我的面吹牛践啄,可吹牛的內(nèi)容都是我干的浇雹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屿讽,長吁一口氣:“原來是場噩夢啊……” “哼昭灵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伐谈,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烂完,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后诵棵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抠蚣,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年履澳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘶窄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓屠。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖护侮,靈堂內(nèi)的尸體忽然破棺而出敌完,到底是詐尸還是另有隱情,我是刑警寧澤羊初,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布滨溉,位于F島的核電站,受9級特大地震影響长赞,放射性物質(zhì)發(fā)生泄漏晦攒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一得哆、第九天 我趴在偏房一處隱蔽的房頂上張望脯颜。 院中可真熱鬧,春花似錦贩据、人聲如沸栋操。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矾芙。三九已至,卻和暖如春近上,著一層夾襖步出監(jiān)牢的瞬間剔宪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工壹无, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留葱绒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓斗锭,卻偏偏與公主長得像地淀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子拒迅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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