• 開車不需要知道離合器是怎麼工作的,但如果知道離合器原理,那麼車子可以開得更平穩。
ReactiveCocoa 是一個重型的 FRP 框架,內容十分豐富,它使用了大量內建的 block,這使得其有強大的功能的同時,內部原始碼也比較複雜。本文研究的版本是2.4.4,小版本間的差別不是太大,無需擔心此問題。 這裡只探究其核心 RACSignal
原始碼及其相關部分。本文不會詳細解釋裡面的程式碼,重點在於討論那些核心程式碼是 怎麼來
的。文字難免有不正確的地方,請不吝指教,非常感謝。
@protocol RACSubscriber
訊號是一個非同步資料流,即一個將要發生的以時間為序的事件序列,它能發射出三種不同的東西:value
、error
、completed
。我們們能非同步地捕獲這些事件:監聽訊號,針對其發出的三種東西進行操作。“監聽”資訊的行為叫做 訂閱(subscriber)。我們定義的操作就是觀察者,這個被“監聽”的訊號就是被觀察的主體(subject) 。其實,這正是“觀察者”設計模式!
RAC 針對這個訂閱行為定義了一個協議:RACSubscriber。RACSubscriber 協議是與 RACSignal 打交道的唯一方式。我們們先不探究 RACSignal 的內容,而是先研究下 RACSubscriber 是怎麼回事。
先來看下 RACSubscriber 的定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 用於從 RACSignal 中直接接收 values 的物件 @protocol RACSubscriber <NSObject> @required /// 傳送下一個 value 給 subscribers。value 可以為 nil。 - (void)sendNext:(id)value; /// 傳送 error 給 subscribers。 error 可以為 nil。 /// /// 這會終結整個訂閱行為,而且接下來也無法再訂閱任何訊號了。 - (void)sendError:(NSError *)error; /// 傳送 completed 給 subscribers。 /// /// 這會終結整個訂閱行為,而且接下來也無法再訂閱任何訊號了。 - (void)sendCompleted; /// 現在重要的是上面三個,先別管這個,忽略掉。 - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; @end |
##1、NLSubscriber
我們們自己來實現這個協議看看(本文自定義的類都以 “NL” 開頭,以視區別):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// NLSubscriber.h @interface NLSubscriber : NSObject <RACSubscriber> @end // NLSubscriber.m @implementation NLSubscriber - (void)sendNext:(id)value { NSLog(@"%s value:%@", sel_getName(_cmd), value); } - (void)sendCompleted { NSLog(@"%s", sel_getName(_cmd)); } - (void)sendError:(NSError *)error { NSLog(@"%s error:%@", sel_getName(_cmd), error); } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { // to nothing } @end |
現在我們們這個類只關心 sendNext:
、 sendError:
和 sendCompleted
。本類的實現只是簡單的列印一些資料。那怎麼來使用這個訂閱者呢?RACSignal
類提供了介面來讓實現了 RACSubscriber
協議的訂閱者訂閱訊號:
1 2 3 4 5 6 7 |
@interface RACSignal (Subscription) /* * `subscriber` 訂閱 receiver 的變化。由 receiver 決定怎麼給 subscriber 傳送事件。 *簡單來說,就是由這個被訂閱的訊號來給訂閱者 subscriber 傳送 `sendNext:` 等訊息。 */ - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber; @end |
用定時器訊號來試試看:
1 2 3 4 5 6 7 8 9 10 |
/** * @brief 建立一個定時器訊號,每三秒發出一個當時日期值。一共發5次。 */ RACSignal *signalInterval = [RACSignal interval:3.0 onScheduler:[RACScheduler mainThreadScheduler]]; signalInterval = [signalInterval take:5]; NLSubscriber *subscriber = [[NLSubscriber alloc] init]; /** * @brief 用訂閱者 subscriber 訂閱定時器訊號 */ [signalInterval subscribe:subscriber]; |
下面是輸出結果:
1 2 3 4 5 6 |
2015-08-15 17:45:02.612 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:02 +0000 2015-08-15 17:45:05.612 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:05 +0000 2015-08-15 17:45:08.615 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:08 +0000 2015-08-15 17:45:11.613 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:11 +0000 2015-08-15 17:45:14.615 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:14 +0000 2015-08-15 17:45:14.615 RACPraiseDemo[738:59818] sendCompleted |
##2、改進NLSubscriber
現在的這個訂閱者類 NLSubscriber 除了列印打東西外,啥也幹不了,更別說複用了,如果針對所有的訊號都寫一個訂閱者那也太痛苦了,甚至是不太可能的事。
我們們來改進一下,做到如下幾點:
1. 實現 RACSubscriber
協議
2. 提供與 RACSubscriber
相 對應的
、可選的
、可配
的介面。
沒錯,這正是一個介面卡!
第2點的要求可不少,那怎麼才能做到這一點呢?還好,OC 中有 block !我們們可以將 RACSubscriber
協議中的三個方法轉為三個 block:
1 2 3 |
- (void)sendNext:(id)value; ----> void (^next)(id value); - (void)sendError:(NSError *)error; ----> void (^error)(NSError *error); - (void)sendCompleted; ----> void (^completed)(void); |
改進目標和改進方向都有了,那我們們來看看改進後的的樣子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
// 標頭檔案 /** * @brief 基於 block 的訂閱者 */ @interface NLSubscriber : NSObject <RACSubscriber> /** * @brief 建立例項 */ + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed; @end // 實現檔案 @interface NLSubscriber () @property (nonatomic, copy) void (^next)(id value); @property (nonatomic, copy) void (^error)(NSError *error); @property (nonatomic, copy) void (^completed)(void); @end @implementation NLSubscriber #pragma mark Lifecycle + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { NLSubscriber *subscriber = [[self alloc] init]; subscriber->_next = [next copy]; subscriber->_error = [error copy]; subscriber->_completed = [completed copy]; return subscriber; } #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]; if (errorBlock == nil) return; errorBlock(e); } } - (void)sendCompleted { @synchronized (self) { void (^completedBlock)(void) = [self.completed copy]; if (completedBlock == nil) return; completedBlock(); } } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { // to nothing } @end |
現在來試試看這個改進版,還是上面那個定時器的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * @brief 建立一個定時器訊號,每三秒發出一個當時日期值。一共發5次。 */ RACSignal *signalInterval = [RACSignal interval:3.0 onScheduler:[RACScheduler mainThreadScheduler]]; signalInterval = [signalInterval take:5]; NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:^(id x) { NSLog(@"next:%@", x); } error:nil completed:^{ NSLog(@"completed"); }]; /** * @brief 用訂閱者 subscriber 訂閱定時器訊號 */ [signalInterval subscribe:subscriber]; |
輸出結果如下:
1 2 3 4 5 6 |
2015-08-15 19:50:43.355 RACPraiseDemo[870:116551] next:2015-08-15 11:50:43 +0000 2015-08-15 19:50:46.358 RACPraiseDemo[870:116551] next:2015-08-15 11:50:46 +0000 2015-08-15 19:50:49.355 RACPraiseDemo[870:116551] next:2015-08-15 11:50:49 +0000 2015-08-15 19:50:52.356 RACPraiseDemo[870:116551] next:2015-08-15 11:50:52 +0000 2015-08-15 19:50:55.356 RACPraiseDemo[870:116551] next:2015-08-15 11:50:55 +0000 2015-08-15 19:50:55.356 RACPraiseDemo[870:116551] completed |
輸出結果沒什麼變化,但是訂閱者的行為終於受到我們們的撐控了。再也不用為了一個訊號而去實現 RACSubscriber
協議了,只需要拿出 NLSubscriber
這個介面卡,再加上我們們想要的自定義的行為即可。如果對訊號發出的某個事件不感興趣,直接傳個 nil 可以了,例如上面例子的 error:
,要知道, RACSubscriber
協議中的所有方法都是 @required
的。NLSubscriber
大大方便了我們的工作。
那還以再改進嗎?
##3、RACSignal 類別之 Subscription
有沒有可能把 NLSubscriber
隱藏起來呢?畢竟作為一個訊號的消費者,需要了解的越少就越簡單,用起來也就越方便。我們們可以通過 OC 中的類別方式,給 RACSignal
加個類別(nl_Subscription
),將訂閱操作封裝到這個訊號類中。這樣,對於使用這個類的客戶而言,甚至不知道訂閱者的存在。
nl_Subscription
類別程式碼如下:
1 |
// .h #import "RACSignal.h" @interface RACSignal (nl_Subscription) - (void)nl_subscribeNext:(void (^)(id x))nextBlock; - (void)nl_subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock; - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; - (void)nl_subscribeError:(void (^)(NSError *error))errorBlock; - (void)nl_subscribeCompleted:(void (^)(void))completedBlock; - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock; - (void)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; @end // .m #import "RACSignal+nl_Subscription.h" #import "NLSubscriber.h" @implementation RACSignal (nl_Subscription) - (void)nl_subscribeNext:(void (^)(id x))nextBlock { [self nl_subscribeNext:nextBlock error:nil completed:nil]; } - (void)nl_subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock { [self nl_subscribeNext:nextBlock error:nil completed:completedBlock]; } - (void)nl_subscribeError:(void (^)(NSError *error))errorBlock { [self nl_subscribeNext:nil error:errorBlock completed:nil]; } - (void)nl_subscribeCompleted:(void (^)(void))completedBlock { [self nl_subscribeNext:nil error:nil completed:completedBlock]; } - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock { [self nl_subscribeNext:nextBlock error:errorBlock completed:nil]; } - (void)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { [self nl_subscribeNext:nil error:errorBlock completed:completedBlock]; } - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; [self subscribe:subscriber]; } @end |
在這個類別中,將訊號的 next:
、error:
和 completed
以及這三個事件的組合都以 block
的形式封裝起來,從以上程式碼中可以看出,這些方法最終呼叫的還是 - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
方法,而它則封裝了訂閱者 NLSubsciber
。
通過這麼個小小的封裝,客戶使用起來就極其方便了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * @brief 建立一個自定義的訊號。 * 這個訊號在被訂閱時,會傳送一個當前的日期值; * 再過三秒後,再次傳送此時的日期值; * 最後,再傳送完成事件。 */ RACSignal *signalInterval = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { [subscriber sendNext:[NSDate date]]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [subscriber sendNext:[NSDate date]]; [subscriber sendCompleted]; }); return (id)nil; }]; [signalInterval nl_subscribeNext:^(id x) { NSLog(@"next:%@", x); } error:^(NSError *error) { NSLog(@"error:%@", error); } completed:^{ NSLog(@"completed"); }]; |
輸出如下:
1 2 3 |
2015-08-16 23:29:44.406 RACPraiseDemo[653:32675] next:2015-08-16 15:29:44 +0000 2015-08-16 23:29:47.701 RACPraiseDemo[653:32675] next:2015-08-16 15:29:47 +0000 2015-08-16 23:29:47.701 RACPraiseDemo[653:32675] completed |
本例並沒有採用之前的 “定時器訊號”,而是自己建立的訊號,當有訂閱者到來時,由這個訊號來決定在什麼時候傳送什麼事件。這個例子裡傳送的事件的邏輯請看程式碼裡的註釋。
看到這裡,是不是很熟悉了?有沒有想起 subscribeNext:
,好吧,我就是在使用好多好多次它之後才慢慢入門的,誰讓 RAC 的大部分教程裡面第一個講的就是它呢!
到了這裡,是不是訂閱者這部分就完了呢?我相信你也注意到了,這裡有幾個不對勁的地方:
1. 無法隨時中斷訂閱操作。想想訂閱了一個無限次的定時器訊號,無法中斷訂閱操作的話,定時器就是永不停止的發下去。
2. 訂閱完成或錯誤時,沒有統一的地方做清理、掃尾等工作。比如現在有一個上傳檔案的訊號,當上傳完成或上傳錯誤時,你得斷開與檔案伺服器的網路連線,還得清空記憶體裡的檔案資料。
##4、Disposable
###RACDisposable
針對上述兩個問題,RACDisposable 應運而生。也就是說 Disposable 有兩個作用:
1. 中斷訂閱某訊號
2. 訂閱完成後,執行一些收尾任務(清理、回收等等)。
訂閱者與 Disposable 的關係:
1. 當 Disposable 有“清理”過,那麼訂閱者就不會再接收到這個被“清理”訂閱源的任何事件。舉例而言,就是訂閱者 subscriberX 訂閱了訊號 signalA 和 signalB 兩個訊號,其所對應的 Disposable 分別為 disposableA 和 disposableB,也就是說 subscriberX 會同時接收來自 signalA 和 signalB 的訊號。當我們手動強制 “清理” disposableA 後,subscriberX 就不會再接收來自 signalA 的任何事件;而來自 signalB 的事件則不受影響。
2. 當訂閱者 subscriberX 有接收來自任何一個訊號的 “error” 或 “completed” 事件時,則不會再接收任何事件了。
可以這麼說:Disposable
代表發生了訂閱行為
根據 Disposable 的作用和與訂閱者的關係,來總結它所需要提供的介面:
1. 包含清理任務的 block ;
2. 執行清理任務的方法:- (void)dispose ;
3. 一個用來表明是否已經 “清理” 過的布林變數:BOOL disposed 。
我們們為這個 Disposable 也整了一個類,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
// .h file /** * @brief 一個 disposable 封裝了用於拆除、清理訂閱的任務工作 */ @interface NLDisposable : NSObject /** * @brief 這個 disposable 是否已經拆除過 */ @property (atomic, assign, getter = isDisposed, readonly) BOOL disposed; + (instancetype)disposableWithBlock:(void (^)(void))block; /** * @brief 執行拆除工作。能多次呼叫這個訊息,只有第一次呼叫有效。 */ - (void)dispose; @end // .m file @interface NLDisposable () { /** * @brief 型別類似於:@property (copy) void (^disposeBlock)(void); * 在 disposal 要執行的任務邏輯。 * 1、如果沒有初媽值的話,則初始值預設為 `self` * 2、當已經 disposal 過後,值為 NULL。 */ void * volatile _disposeBlock; } @end @implementation NLDisposable #pragma mark Properties - (BOOL)isDisposed { return _disposeBlock == NULL; } #pragma mark Lifecycle - (id)init { self = [super init]; if (self == nil) return nil; _disposeBlock = (__bridge void *)self; OSMemoryBarrier(); return self; } - (id)initWithBlock:(void (^)(void))block { NSCParameterAssert(block != nil); self = [super init]; if (self == nil) return nil; _disposeBlock = (void *)CFBridgingRetain([block copy]); OSMemoryBarrier(); return self; } + (instancetype)disposableWithBlock:(void (^)(void))block { return [[self alloc] initWithBlock:block]; } - (void)dealloc { if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; CFRelease(_disposeBlock); _disposeBlock = NULL; } #pragma mark Disposal - (void)dispose { /** * @brief 這裡為了讓邏輯更清晰,去掉了原子性操作。具體程式碼請看 RACDisposable */ if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; void (^disposeBlock)(void) = CFBridgingRelease((void *)_disposeBlock); _disposeBlock = NULL; disposeBlock(); } @end |
從這個類提供的介面來看,顯然是做不到 “訂閱者與 Disposable 的關係” 中的第2條的。因為這條中所描述的是一個訂閱者訂閱多個訊號,且能手動中斷訂閱其中一個訊號的功能,而 NLDisposable 是單個訂閱關係所設計的。
###RACCompoundDisposable
那怎麼組織這“多個”的關係呢?陣列?Good,就是陣列。OK,我們們來相像一下這個方案的初步程式碼。每個訂閱者有一個 Disposable 陣列,訂閱一個一個訊號,則加入一個 Disposable;當手動拆除一個訂閱關係時,找到與之相關的 Disposable,傳送 dispose 訊息,將其從陣列中移除;當訂閱者不能再接收訊息時(接收過 error
或 completed
訊息),要 dispose 陣列中所有元素,接下來再加入元素時,直接給這個要加入的元素髮送 dispose 訊息;在多執行緒環境下,每一次加入或移除或其遍歷時,都得加鎖。。。(好吧,我編不下去了)
我** ,這麼複雜,看來直接用陣列來維護是不可行的了。有啥其它可行的法子沒?還好,GoF 對此有個方案,叫做“組合模式”:
組合模式 允許你將物件組合成樹形結構來表現 “整體/部分” 層次結構。組合能讓客戶以一致的方式處理個別物件以及物件組合。
使用組合結構,我們能把相同的操作應用在組合和個別物件上。換句話說,在大多數情況下,我們可以 忽略 物件組合和個別物件之間的差別。
本文畢竟不是來講模式的,關於這個模式更多的資訊,請自行 google。
RAC 中這個組合類叫 RACCompoundDisposable
, 我們們的叫 NLCompoundDisposable
,來看看我們們這個類的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
// .h file #import "NLDisposable.h" /** * @brief A disposable of disposables。當它 dispose 時,它會 dispose * 它所包含的所有的 disposables。 * * 如果本 compound disposable 已經 dispose 過後,再來呼叫 -addDisposable:, * 那麼其引數 disposable 會立馬呼叫 dispose 方法。 * * 本類中的方法說明請檢視 RACCompoundDisposable 中的同名方法。 * 本類與真正的類 RACCompoundDisposable 程式碼差別較大,但本質是一樣的 */ @interface NLCompoundDisposable : NLDisposable /** * @brief 建立並返回一個新的 compound disposable。 */ + (instancetype)compoundDisposable; /** * @brief 建立並返回一個新的包含了 disposables 的 compound disposable。 * * @param disposables disposable 陣列 * * @return 一個新的 compound disposable */ + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables; /** * @brief 將 disposable 加到本 compound disposable 中。如果本 compound disposable * 已經 dispose 的話,那麼引數 disposable 會被立即 dispose。 * * 本方法是執行緒安全的。 * * @param disposable 要被加入的 disposable。如果它為 nil 的話,那麼什麼也不會發生。 */ - (void)addDisposable:(NLDisposable *)disposable; /** * @brief 從本 compound disposable 移除指定的 disposable(不管這個 disposable 是什麼 * 狀態);如果這個 disposable 不在本 compound disposable 中,則什麼也不會發生。 * * 本方法是執行緒安全的。 * * @param disposable 要被移除的 disposable。可以為 nil。 */ - (void)removeDisposable:(NLDisposable *)disposable; @end // .m file #import "NLCompoundDisposable.h" #import <libkern/OSAtomic.h> @interface NLCompoundDisposable () { /** * @brief 同步鎖 */ OSSpinLock _spinLock; /** * @brief 本 compound disposable 所包含的 disposables。 * * 在操作這個陣列時,應該使用 _spinLock 進行同步。如果 * `_disposed` 為 YES,則這個陣列可能為 nil。 */ NSMutableArray *_disposables; /** * @brief 本 compound disposable 是否已經 dispose 。 * * 在操作這個變數時,應該使用 _spinLock 進行同步。 */ BOOL _disposed; } @end @implementation NLCompoundDisposable #pragma mark Properties - (BOOL)isDisposed { OSSpinLockLock(&_spinLock); BOOL disposed = _disposed; OSSpinLockUnlock(&_spinLock); return disposed; } #pragma mark Lifecycle + (instancetype)compoundDisposable { return [[self alloc] initWithDisposables:nil]; } + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { return [[self alloc] initWithDisposables:disposables]; } - (id)initWithDisposables:(NSArray *)otherDisposables { self = [self init]; if (self == nil) return nil; if ([otherDisposables count]) { _disposables = [NSMutableArray arrayWithArray:otherDisposables]; } return self; } - (id)initWithBlock:(void (^)(void))block { NLDisposable *disposable = [NLDisposable disposableWithBlock:block]; return [self initWithDisposables:@[ disposable ]]; } - (void)dealloc { _disposables = nil; } #pragma mark Addition and Removal - (void)addDisposable:(NLDisposable *)disposable { NSCParameterAssert(disposable != self); if (disposable == nil || disposable.disposed) return; BOOL shouldDispose = NO; OSSpinLockLock(&_spinLock); { if (_disposed) { shouldDispose = YES; } else { if (_disposables == nil) { _disposables = [NSMutableArray array]; } [_disposables addObject:disposable]; } } OSSpinLockUnlock(&_spinLock); if (shouldDispose) { [disposable dispose]; } } - (void)removeDisposable:(NLDisposable *)disposable { if (disposable == nil) return; OSSpinLockLock(&_spinLock); { if (!_disposed) { if (_disposables != nil) { [_disposables removeObject:disposable]; } } } OSSpinLockUnlock(&_spinLock); } #pragma mark RACDisposable - (void)dispose { NSArray *remainingDisposables = nil; OSSpinLockLock(&_spinLock); { _disposed = YES; remainingDisposables = _disposables; _disposables = nil; } OSSpinLockUnlock(&_spinLock); if (remainingDisposables == nil) return; [remainingDisposables makeObjectsPerformSelector:@selector(dispose)]; } @end |
###RACScheduler 簡介
本文不打算研究 RACScheduler 原始碼,但其又是 RAC 中不可或缺的一個元件,在研究 RACSignal 的原始碼時不可避免地會遇到它,所以對其作下介紹還是有必要的。其實它的原始碼並不複雜,可自行研究。
ReactiveCocoa 中 RACSignal 傳送的所有事件的傳遞交給了一個特殊的框架元件——排程器,即 RACScheduler 類簇(類簇模式稍後介紹)。排程器是為了簡化 同步/非同步/延遲 事件傳遞 以及 取消預定的任務(scheduded actions) 這兩種 RAC 中常見的動作而提出來的。“事件傳遞” 簡單而言就是些 blocks,RACScheduler 所做的就是:排程這些 blocks (schedule blokcs,還是英文的意思準確些)。我們可以通過那些排程方法所返回的 RACDisposable
物件來取消那些 scheduling blocks。
正如前面所說,RACScheduler 是一個類簇。我們們來看看幾種具體的排程器:
####RACImmediateScheduler
這是 RAC 內部使用的私有排程器,只支援同步 scheduling。就是簡單的馬上執行 block。這個偵錯程式的延遲 scheduling 是通過呼叫 -[NSThread sleepUntilDate:]
來阻塞當前執行緒來達到目的的。顯然,這樣一個排程器,沒法取消 scheduling,所以它那些方法返回的 disposables 啥也不會做(實際上,它那些 scheduling 方法返回的是nil)。
####RACQueueScheduler
這個排程器使用 GCD 佇列來 scheduling blocks。如果你對 GCD 有所瞭解的話,你會發現這個排程器的功能很簡單,它只是在 GCD 佇列 dispatching blocks 上的簡單封裝罷了。
####RACSubscriptionScheduler
這是另一個內部使用的私有排程器。如果當前執行緒有排程器(排程器可以與執行緒相關聯起來:associated)那它就將 scheduling 轉發給這個執行緒的排程器;否則就轉發給預設的 background queue 偵錯程式。
####介面
偵錯程式有下面一些方法:
1 2 3 4 |
- (RACDisposable *)schedule:(void (^)(void))block; - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block; - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block; - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block; |
scheduling block 如下:
1 2 3 4 5 6 |
RACDisposable *disposable = [[RACScheduler mainThreadScheduler] afterDelay:5.0 schedule:^{ // do something }]; // 如果你想要取消 scheduling block [disposable dispose]; // block scheduling 被取消了,不會再被執行。 |
##5、Subscriber 和 Disposable
前面介紹了 Disposable 的來源,現在來研究下怎麼使用它。還記得嗎,訂閱者與訊號打交道的唯一方式是 RACSignal
中的一個方法:
1 |
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber; |
自定義訊號所對應的類是 RACDynamicSignal
。RACSignal
採用的是類簇模式。除自定義訊號之外還有幾種其它的訊號,之後會研究到。OC 中的 NSNumber
用的就是類簇模式。類簇是Foundation框架中廣泛使用的設計模式。類簇將一些私有的、具體的子類組合在一個公共的、抽象的超類下面,以這種方法來組織類可以簡化一個物件導向框架的公開架構,而又不減少功能的豐富性。
我們們來研究一下自定義訊號裡的這個方法的實現。這個方法實現的難處在於:“一個訂閱者可以訂閱多個訊號,並可以手動拆除其中任何一個訂閱”。針對這個問題,提出了上節講到的 RACDisposable
。也就是說,在每一次訂閱時,都會返回一個與這次訂閱相關的 Disposable
,那怎麼做到這一點呢?
給訂閱者新增一個 CompoundDisposable
型別的屬性 (畢竟 CompoundDisposable
就是用來針對多個 Disposable
的統一管理而存在的),然後在每一次訂閱時,都加一個 Disposable
到這個屬性裡,行不行?但很可惜,訂閱者是一個協議 protocol RACSubscriber
,而不是一個具體的類,我們們在使用到它時,都是別人實現了這個協議的類的物件,所以我們們不太可能做到說給這麼一個未知的類新增一個屬性。
事實上,RAC 中確實有
RACSubscriber
這麼一個私有類(它是我們們第一個自定義類NLSubscriber
的原型),我們們叫它做class RACSubscriber
。嗯,class RACSusbscriber
實現了protocol RACSubscriber
協議:@interface RACSubscriber : NSObject <RACSubscriber>
。有沒有想到class NSObject
和protocol NSObject
?雖然它們形式上確實很像,但千萬別混為一談。RAC 中的其它實現了protocol RACSubscriber
協議的訂閱者類可沒有一個繼承自class RACSubscriber
的。
我們們可以用裝飾模式來解決這個問題
裝飾模式。在不必改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能。
訂閱者裝飾器 RACPassthroughSubscriber
在訂閱者每一次訂閱訊號時產生一個 Disposable
,並將其與此次訂閱關聯起來,這是通過裝飾器 RACPassthroughSubscriber
來做到的。這個裝飾器的功能:
1. 包裝真正的訂閱者,使自己成為訂閱者的替代者。
2. 將真正的訂閱者與一個訂閱時產生的 Disposable
關聯起來。
這正是一個裝飾器所應該做的。依之前的,我們們來模仿這個裝飾器,新建一個我們們的裝飾器:NLPassthroughSubscriber
,來看下它的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
// .f file @class RACCompoundDisposable; @class RACSignal; /** * @brief 這是一個訂閱者的裝飾器。在沒有 dispose 時,它會把接收到的所有 * 的事件都轉發給真實的訂閱者。 */ @interface NLPassthroughSubscriber : NSObject <RACSubscriber> /** * @brief 初始化方法 * * @param subscriber 被包裝的真實的訂閱者,本裝飾器會把接收到的所有的事件都轉發給這個訂閱者。不能為 nil * @param signal 要傳送事件給這個裝飾器的訊號。 * @param disposable 當這個 disposable 接收到 dispose 訊息後,將不會再轉發事件。不能為 nil * * @return 返回一個初始化後的 passthrough subscriber */ - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable; @end // .m file @interface NLPassthroughSubscriber () // 被轉發事件的訂閱者。 @property (nonatomic, strong, readonly) id<RACSubscriber> innerSubscriber; // 要傳送事件給本裝飾器的訊號 @property (nonatomic, unsafe_unretained, readonly) RACSignal *signal; // 代表當前訂閱關係的 disposable。當它 dispose 後,將不會再轉發任何事件給 `innerSubscriber`。 @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; @end @implementation NLPassthroughSubscriber #pragma mark Lifecycle - (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` 傳給訂閱者。 */ [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; return self; } #pragma mark RACSubscriber - (void)sendNext:(id)value { /** * 如果 disposable 已經 dispose 過,就不再轉發事件 */ if (self.disposable.disposed) return; /** * 轉發 next 事件 */ [self.innerSubscriber sendNext:value]; } - (void)sendError:(NSError *)error { if (self.disposable.disposed) return; [self.innerSubscriber sendError:error]; } - (void)sendCompleted { if (self.disposable.disposed) return; [self.innerSubscriber sendCompleted]; } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { if (disposable != self.disposable) { [self.disposable addDisposable:disposable]; } } @end |
自定義訊號 RACDynamicSignal 的訂閱方法 subscribe
我們們來看看 RACDynamicSignal
是怎麼來使用 RACPassthroughSubscriber
的,這裡就不自己寫程式碼了,直接上它的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCParameterAssert(subscriber != nil); /** * 本次訂閱相關 disposable。本方法的返回值,起 拆除 本次訂閱的作用。 */ RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; /** * 訂閱者裝飾器。 */ subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; /** * _didSubscriber 是在 `+ createSignal` 方法中進入的 block 引數。 * + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; * 這個 block 以 subscriber 為引數,返回一個 disposable,即 innerDisposable,而這個 innerDisposable * 的作用是在 subscriber 不再訂閱本 signal 時,起回收資源的作用。 */ if (self.didSubscribe != NULL) { RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; } return disposable; } |
可以看到,訂閱者裝飾器直接偽裝成真正的訂閱器,傳給 didSubscribe 這個 block 使用。在這個 block 中,會有一些事件傳送給訂閱者裝飾器,而這個訂閱者裝飾器則根據 disposable 的狀態來來決定是否轉發給真正的訂閱者。disposable 作為返回值,返回給外部,也就是說能夠從外部來取消這個訂閱了。
1 2 3 4 5 6 |
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; |
從這幾行程式碼中,我們可以看到,didSubscribe
這個 block 是處於 subscriptionScheduler
這個 scheduler 的排程中。RACSubscriptionScheduler
的排程是取決於當前所在的執行緒的,即 didSubscribe
可能會在不同的排程器中被執行。
假設當前 -(RACDisposable *)subscribe:(id<RACSubscriber>)subscriber
這個方法是在非同步環境下呼叫的,那麼在 disposable
返回後,在schedule block 還沒有來得及呼叫,此時 disposable
中包含 schedulingDisposable
。如果我們此時給 disposable
傳送 dispose
訊息,那麼 schedulingDisposable
也會被 dispose
,schedule block 就不會執行了;如果是在 schedule block 執行中或執行後給 disposable
傳送 dispose
訊息,那麼 innerDisposable
和 schedulingDisposable
都會被 dispose
。這些行為正是我們們所預期的。
##6、再次改進NLSubscriber
###1、didSubscribeWithDisposable
1 2 3 4 |
@protocol RACSubscriber <NSObject> @required - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; @end |
這個 RACSubscriber
協議中宣告的一個方法,在最開始的時候被我們特意給忽略,現在是時候回過頭來看看它了。對於一個訂閱者來說,next
、error
和 completed
三種事件分別對應協議裡的三種方法,那麼這個方法存在的意義是什麼呢?
從 RACSubscriber
協議中,可以看到,當一個訂閱者有收到過 error
或 completed
事件後,這個訂閱者就不能再接收任何事件了,換句話說,此時這個訂閱者會解除所有的訂閱關係,且無法再次訂閱。既然要解除所有訂閱,首先我得知道我訂閱過哪些訊號是不?而代表一個訂閱行為的就是 disposable
,告訴它就傳一個給它好了。所以這個方法就是告訴訂閱者:你發生了訂閱行為。
那為啥要 RACCompoundDisposable
型別作為引數呢?因為有些訂閱者會針對其附加一些操作,而只有這個型別的 disposable
才能動態加入一些操作。接下來我們就會看到的。
###2、NLSubscriber 結合 RACDisposable
這一次改進 NLSubscriber
的目的是讓其可以終結自己的訂閱能力的功能。同時實現 didSubscribeWithDisposable
方法。千言萬語不如實際程式碼,讓我們來一探究竟:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
#import "NLSubscriber.h" #import <ReactiveCocoa.h> @interface NLSubscriber () @property (nonatomic, copy) void (^next)(id value); @property (nonatomic, copy) void (^error)(NSError *error); @property (nonatomic, copy) void (^completed)(void); /** * @brief 代表訂閱者本身總體訂閱行為的 disposable。 * 當有接收到 error 或 completed 事件時,應該 dispose 這個 disposable。 */ @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; @end @implementation NLSubscriber #pragma mark Lifecycle + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { NLSubscriber *subscriber = [[self alloc] init]; subscriber->_next = [next copy]; subscriber->_error = [error copy]; subscriber->_completed = [completed copy]; return subscriber; } - (instancetype)init { self = [super init]; if (self == nil) return nil; @unsafeify(self); /** * 當 _disposable 被髮送 dispose 訊息時,將 next、error 和 completed 這三個 * block 設定為 nil,從而間實現訂閱者無法再接收任何事件的功能。 */ 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]; } #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]; [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(); } } - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable { if (otherDisposable.disposed) return; /** * 將 otherDisposable 新增到 selfDisposable 中。這樣當 selfDisposable 被 dispose 時, * otherDisposable 也能被 dispose。 * * 這樣,當訂閱者接收到 error 或 completed 事件時,就能解除這個訂閱者自身的所有訂閱行為了。 */ RACCompoundDisposable *selfDisposable = self.disposable; [selfDisposable addDisposable:otherDisposable]; @unsafeify(otherDisposable); /** * 如果這個訂閱行為被解除,就將 otherDisposable 從 selfDisposable 中移除。 * (我們給 otherDisposable 增加了行為,這也就是引數需要是 RACCompoundDisposable * 型別的原因了。當然,其它的訂閱者怎麼用這個引數就跟其實際的業務相關了。) */ [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{ @strongify(otherDisposable); [selfDisposable removeDisposable:otherDisposable]; }]]; } @end |
###3、改進類別 nl_Subscription
還記得麼?nl_Subscription
類別中的訂閱方法一旦訂閱,就無法停止了,這顯然有很大的問題。解決這個問題很簡單,直接將 disposable
返回即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// .h file @interface RACSignal (nl_Subscription) ... - (RACDisposable *)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; @end // .m file @implementation RACSignal (nl_Subscription) ... - (RACDisposable *)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; return [self subscribe:subscriber]; } |
RACSignal – Operations
本節主要研究這些操作(Operations) —— flattenMap:
、map:
、 filter:
….
終於看到你想看的東西了?好吧,我承認,上節的東西很無趣,可能壓根不是你想看的東西。但如果沒弄清上面的內容的話,直接研究 Operations 可是會比較吃力的喲~
你以為我們們現在開始研究 Operations?哈哈,你又得失望了~ 我們得先看看這兩個類:RACEmptySignal
和 RACReturnSignal
。
##1、兩個 RACSignal 的特殊子類 RACEmptySignal 和 RACReturnSignal
###1、RACEmptySignal
RACEmptySignal
是 +[RACSignal empty]
的內部實現,一個私有 RACSignal
子類。它就是一個會立即 completed
的訊號。讓我們來看看它的 - subscribe:
方法:
1 2 3 4 5 6 7 8 9 10 |
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCParameterAssert(subscriber != nil); /** * 只要一訂閱,就給 subscriber 傳送 completed 事件。 */ return [RACScheduler.subscriptionScheduler schedule:^{ [subscriber sendCompleted]; }]; } |
這樣一個訂閱者一訂閱就會 completed
訊號有什麼用呢?稍後揭曉。
###2、RACReturnSignal
RACReturnSignal
是 +[RACSignal return:]
的內部實現,也是一個私有 RACSignal
子類。它會同步傳送出一個值(即 next
)給訂閱者,然後再傳送 completed
事件。 它比 RACEmptySignal
多了一點點東西,它是。直接看其實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@interface RACReturnSignal () // 在本訊號被訂閱時會傳送的值。 @property (nonatomic, strong, readonly) id value; @end @implementation RACReturnSignal #pragma mark Lifecycle + (RACSignal *)return:(id)value { RACReturnSignal *signal = [[self alloc] init]; signal->_value = value; return signal; } #pragma mark Subscription - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCParameterAssert(subscriber != nil); return [RACScheduler.subscriptionScheduler schedule:^{ [subscriber sendNext:self.value]; [subscriber sendCompleted]; }]; } @end |
純吐槽:為啥要叫 ReturnSignal
呢?不如直接 OneValueSignal
好了。O(∩_∩)O~~ 不過說真的,RAC 的命名真心不咋地。
那麼傳送一個 next
後又 completed
的訊號又有啥用呢?等下會知道地。
##2、concat: 練手
-[RACSignal concat:]
是原始碼較簡單,且使用頻率也較多的。那我們們就來拿它來練練手好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (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]; } |
RACSerialDisposable
是 RACDisposable
的子類,它包含一個 Disposable
,能夠在執行時設定這個 Disposable
。當設定新的 newDisposable
時,老的 oldDisposable
會被 dispose
。當 RACSerialDisposable
被 dispose
時,其所包含的 Disposable
會被 dispose
。
基本上,對一個 RACSignal
的操作的返回值是一個新的 RACSignal
值時,其內部都是呼叫了 +[RACSignal createSignal:]
這個方法。這個建立訊號返回的實際是自定義訊號:RACDynamicSignal
,針對它前文有所介紹。
這裡有一個小技巧。因為很多訊號的操作是針對該訊號本身 self
所傳送的值作的操作。那也就是說會訂閱 self
,那我們們先找到這一句再說:self subscribe:
或 self subscribeNext:...
。嗯,找到了這幾行:
1 2 3 4 5 6 7 8 |
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ RACDisposable *concattedDisposable = [signal subscribe:subscriber]; serialDisposable.disposable = concattedDisposable; }]; |
在訂閱了 self
後,將 next
和 error
事件傳送給訂閱者 subscriber
。當 self
傳送了 completed
事件事,再讓 subscriber
訂閱引數 signal
。也就是當源訊號完成後訂閱 signal
。怎麼樣,很簡單吧。
##3、zipWith:
再來一個練手的玩意。-[RACSignal zipWith:]
比 -[RACSignal concat:]
稍微複雜點。它是將 self
和 引數 signal
兩個訊號傳送的值合併起來傳送給訂閱者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
- (RACSignal *)zipWith:(RACSignal *)signal { NSCParameterAssert(signal != nil); return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { __block BOOL selfCompleted = NO; NSMutableArray *selfValues = [NSMutableArray array]; __block BOOL otherCompleted = NO; NSMutableArray *otherValues = [NSMutableArray array]; void (^sendCompletedIfNecessary)(void) = ^{ @synchronized (selfValues) { BOOL selfEmpty = (selfCompleted && selfValues.count == 0); BOOL otherEmpty = (otherCompleted && otherValues.count == 0); if (selfEmpty || otherEmpty) [subscriber sendCompleted]; } }; void (^sendNext)(void) = ^{ @synchronized (selfValues) { if (selfValues.count == 0) return; if (otherValues.count == 0) return; RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0]); [selfValues removeObjectAtIndex:0]; [otherValues removeObjectAtIndex:0]; [subscriber sendNext:tuple]; sendCompletedIfNecessary(); } }; 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(); } }]; 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]; } @end |
同樣的,重點在 [self subscriberNext:]
和 [signal subscribeNext:]
處。這裡的實現是訂閱 self
和 signal
訊號,然後將它們傳送出的值收集起來,當兩個都發出了值時,分別拿出兩個訊號最早發出的值,合併為一個 RACTuple
,再傳送給訂閱者 subscriber
。這個也很簡單吧,只是程式碼稍多點而已。
##4、bind:
###1、說明
訊號的很多 operations 的實現呼叫來呼叫去最後都是呼叫了這個 -[RACSignal bind:]
方法,比如 flattenMap:
、map:
、filter
等等。那我們們就來看看這個方法是哪路神仙?
這是在 RACStream
中宣告的抽象方法。來看看它的宣告:
1 2 |
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop); - (instancetype)bind:(RACStreamBindBlock (^)(void))block; |
RACStreamBindBlock
是一個 block。它從一個 RACStream
中接收一個值,並且返回一個與該流相同型別的例項。如果將 stop
設為 YES
,則會在返回一個例項後終結此次 bind
。如果返回 nil
則會立即終結。
bind:
方法是將流中每一個值都放到 RACStreamBindBlock
中跑一下。來看看其引數:block
。然而這有什麼卵用呢?好吧,我太笨,從它的說明來看,我真的不能理解它有什麼用。
###2、原始碼解讀
既然從方法說明了解不到,那直接來看其原始碼了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block { NSCParameterAssert(block != NULL); return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACStreamBindBlock bindingBlock = block(); NSMutableArray *signals = [NSMutableArray arrayWithObject:self]; RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; // 三. void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { BOOL removeDisposable = NO; @synchronized (signals) { [signals removeObject:signal]; if (signals.count == 0) { [subscriber sendCompleted]; [compoundDisposable dispose]; } else { removeDisposable = YES; } } if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; }; // 二. void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { @synchronized (signals) { [signals addObject:signal]; } RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; RACDisposable *disposable = [signal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [compoundDisposable dispose]; [subscriber sendError:error]; } completed:^{ @autoreleasepool { completeSignal(signal, selfDisposable); } }]; selfDisposable.disposable = disposable; }; @autoreleasepool { RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; // 一. RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { // Manually check disposal to handle synchronous errors. if (compoundDisposable.disposed) return; BOOL stop = NO; id signal = bindingBlock(x, &stop); @autoreleasepool { if (signal != nil) addSignal(signal); if (signal == nil || stop) { [selfDisposable dispose]; completeSignal(self, selfDisposable); } } } error:^(NSError *error) { [compoundDisposable dispose]; [subscriber sendError:error]; } completed:^{ @autoreleasepool { completeSignal(self, selfDisposable); } }]; selfDisposable.disposable = bindingDisposable; } return compoundDisposable; }] setNameWithFormat:@"[%@] -bind:", self.name]; } |
我們一步一步來看。先從第 一 步開始,其步驟如下:
1. 訂閱 self
2. 針對 self
發出的每一個值 x
,經過 bindingBlock
,獲取一個訊號:signal
1. 如果 signal
不為 nil,就轉到第二步:addSignal
2. 如果 signal
為 nil,或 stop
為 YES
,則轉到第三步:completedSignal
3. 如果 self
發出 error
事件,則中斷訂閱;如果 self
發出 completed
事件則轉到第三步:completedSignal
第二步:addSignal:signal
1. 先將 signal
新增到 signals
中
2. 訂閱 signal
1. 將 signal
的 next
事件轉發給訂閱者 subscriber
2. 如果 signal
傳送 error
事件則中斷訂閱
3. 如果 signal
傳送 complete
事件,則轉到第三步
第三步:completeSignal:signal:disposable
1. 將 signal
從 signals
中移除
2. 如果 signals
中沒有了 signal
,那麼訂閱就完成了
好了,來總結一下這個 -bind:
:
1. 訂閱原訊號 self
的 values。
2. 將 self
發出的任何一個值,都對其使用 bindingBlock
進行轉換。
3. 如果 bindingBlock
返回一個訊號,則訂閱它,將從它那接收到的每個值都傳遞給訂閱者 subscriber
。
4. 如果 bindingBlock
要求結束繫結,則 complete self
訊號。
5. 如果 所有 的訊號全都 complete,則給 subscriber
傳送 completed
事件.
6. 如果任何一個訊號發出 error
,將其傳送給 subscriber
。
那從中可以玩出什麼花樣呢?
###3、示例
我們們先用用它,再看看能怎麼玩吧。
####示例1:結合 RACReturnSignal
1 2 3 4 5 6 7 8 9 10 11 12 |
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3]; RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{ return ^(id value, BOOL *stop) { NSLog(@"inner value: %@", value); return [RACSignal return:value]; }; }]; [bindSignal subscribeNext:^(id x) { NSLog(@"outer value: %@", x); }]; |
輸出如下:
1 2 3 4 5 6 |
2015-08-27 17:16:17.933 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:17 +0000 2015-08-27 17:16:17.934 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:17 +0000 2015-08-27 17:16:18.931 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:18 +0000 2015-08-27 17:16:18.931 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:18 +0000 2015-08-27 17:16:19.931 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:19 +0000 2015-08-27 17:16:19.931 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:19 +0000 |
這個示例就是在 bind:
中簡單的返回值。那我們們將這個值變化一下如何?
####示例2:結合 RACReturnSignal
、轉換 value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3]; RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{ return ^(NSDate *value, BOOL *stop) { NSLog(@"inner value: %@", value); NSTimeInterval nowTime = [value timeIntervalSince1970]; return [RACSignal return:@(nowTime)]; }; }]; [bindSignal subscribeNext:^(id x) { NSLog(@"outer value: %@", x); }]; |
輸出如下:
1 2 3 4 5 6 |
2015-08-27 17:34:04.938 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:04 +0000 2015-08-27 17:34:04.939 RACPraiseDemo[3153:176383] outer value: 1440668044.936496 2015-08-27 17:34:05.939 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:05 +0000 2015-08-27 17:34:05.939 RACPraiseDemo[3153:176383] outer value: 1440668045.939163 2015-08-27 17:34:06.941 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:06 +0000 2015-08-27 17:34:06.941 RACPraiseDemo[3153:176383] outer value: 1440668046.941275 |
哇哇,這就是個 map:
有木有? 現在,有感受到 RACReturnSignal
的魅力?RACReturnSignal
和 -bind:
結合能轉換 value。
####示例3:結合 RACEmptySignal
現在來換個玩法試試看,這回換 RACEmptySignal
來玩玩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3]; __block NSUInteger count = 0; RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{ return ^(NSDate *value, BOOL *stop) { NSLog(@"inner value: %@", value); ++count; if (count % 2 == 0) { return [RACSignal empty]; } return [RACSignal return:value]; }; }]; [bindSignal subscribeNext:^(id x) { NSLog(@"outer value: %@", x); }]; |
輸出如下:
1 2 3 4 5 |
2015-08-27 17:53:45.345 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:45 +0000 2015-08-27 17:53:45.346 RACPraiseDemo[3363:188270] outer value: 2015-08-27 09:53:45 +0000 2015-08-27 17:53:46.345 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:46 +0000 2015-08-27 17:53:47.342 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:47 +0000 2015-08-27 17:53:47.342 RACPraiseDemo[3363:188270] outer value: 2015-08-27 09:53:47 +0000 |
這一次,“outer value” 比 “inner value” 少了一個,這就是 filter:
呀!RACEmptySignal
與 bind:
結合能過濾 value。
####示例4:改進 bind:
經過這幾個示例,我們可以發現,直接使用 bind:
是比較麻煩的。而一般情況下,我們們還真用不到 stop
,那我們們就改進一下唄:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (instancetype)flattenMap:(RACStream * (^)(id value))block { Class class = self.class; return [[self bind:^{ return ^(id value, BOOL *stop) { /** * 如果 block 返回 nil,得用 RACEmptySignal 代替, * 不然會結束 `bind:` */ 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:
了。不必過多解釋了吧~
##5、-map:
嗯,這其實就是 -flattenMap:
與 RACReturnSignal
的結合:
1 2 3 4 5 6 7 8 9 |
- (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]; } |
##6、-flatten
訊號可以傳送任何型別的值,當然也包括 RACSignal
型別。例如,RACCommand
的 executionSignals
這個訊號,它發出的值就是 RACSignal
型別的。對於這種發出的值是 RACSignal
型別的 RACSignal
,叫做 signal of signals。這有點類似於 disposable of disposables。
既然這個訊號發出的就是 RACSignal
,那在 -flattenMap:
中,我們直接將 value 返回就好了。來看看示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * 有效三次的間隔為1秒定時器訊號,此時 signalInterval 是一個 signal of NSDates */ RACSignal *signalInterval = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:3]; /** * 將定時器訊號裡的值修改成 RACSignal 型別 * 此時,signalInterval 變成了一個 signal of signals */ signalInterval = [signalInterval map:^id(NSDate *date) { return [RACSignal return:date]; }]; /** * 既然 signalInterval 裡的值都是訊號,那直接將這些訊號返回即可 */ RACSignal *signal = [signalInterval flattenMap:^RACStream *(RACSignal *returnSignal) { return returnSignal; }]; /** * 由於 signalInterval 裡的值都是包含了一個 NSDate 值的 RACReturnSignal, * 經過 `-flattenMap:` 過後,signal 就變成了 signal of NSDates。 */ [signal subscribeNext:^(id x) { NSLog(@"value: %@", x); }]; |
輸出如下:
1 2 3 |
2015-08-27 21:16:29.517 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:29 +0000 2015-08-27 21:16:30.516 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:30 +0000 2015-08-27 21:16:31.516 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:31 +0000 |
##7、小結
RACSignal
的 operations 實在太多,全部在這裡列出來不現實,也沒有這個必要。我相信,經過前面的解析,你現在再去看其它 的一個 operation 原始碼,也應該不是太大的難事。
#RAC() 巨集展開
RAC 的最大的魅力之一就是繫結:RAC(self, ...) = signal;
這應該是大家經常寫的一條語句。有沒有想過它是怎麼工作的呢?我們們來看點程式碼:
1 2 3 4 5 6 7 8 9 |
@property (nonatomic, strong) NSString *text; //-------- RACSignal *signal = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:3]; signal = [signal map:^id(id value) { return [value description]; }]; RAC(self, text) = signal; |
重點在 RAC(self, text) = signal;
這一行。先來看看將這個巨集展開是什麼樣子(RAC 對巨集的運用很是牛B,有興趣請看這篇文章):
1 |
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self) nilValue:nil][@"text"] = signal; |
看得更清楚一點:
1 2 |
RACSubscriptingAssignmentTrampoline *assignment = [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self) nilValue:nil]; assignment[@"text"] = signal; |
跳到 RACSubscriptingAssignmentTrampoline
類的宣告,可以看到:
1 2 3 4 5 6 |
@interface RACSubscriptingAssignmentTrampoline : NSObject - (id)initWithTarget:(id)target nilValue:(id)nilValue; // 這是可以使用下標 `[]` 語法的關鍵 - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; @end |
這個類使用了 clang 的特性,可以使用 []
語法([]
的相關文章)。也就是說 assignment[@"text"] = signal;
,實際上是這樣子的:
1 |
[assignment setObject:signal forKeyedSubscript:@"text"]; |
再看 - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath;
這個方法的實現,我們發現,它其實呼叫的是 signal
的方法:- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue
,再像上面的方法一樣來分析這個方法,我們找到了關鍵點:
1 2 3 4 5 6 7 8 |
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue { ... RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { ... [object setValue:x ?: nilValue forKeyPath:keyPath]; }... ... } |
哦,原來它就是訂閱了 signal
,並將 signal
發出的每一值都設定給 object
的 keyPath
屬性而已。很簡單嘛~
#結束
本文研究了 RAC 中的一些基本元件,並沒有對一些高階內容進行深入研究,所以才叫“淺析”。但這些也是對高階內容深入研究的基礎,既然有“漁”,何懼無“魚”呢?
其實頗想繼續分享,但心有餘而力不足。
還可研究的主題:
1. Subjects
它也是 RACSignal
一些操作的基礎,值得研究。難度係數:2 (最高為5)
2. RACMulticastConnection
常用,值得研究。難度係數:3
3. Foundation、UIKit、KVO (給各系統類加的 rac_ 擴充套件),有研究價值。研究過後,你會對 runtime 會有很深入的瞭解,還會接觸到一些 OC 中少用的知識(如 NSProxy 等),能開拓視野。難度係數:5