背景
ReactiveCocoa(簡稱RAC)是最初由GitHub團隊開發的一套基於Cocoa的FRP框架。FRP即Functional Reactive Programming(函式式響應式程式設計),其優點是用隨時間改變的函式表示使用者輸入,這樣就不需要可變狀態了。我們之前的文章《RACSignal 的 Subscription 深入分析》裡曾經詳細講解過RAC核心概念之一RACSignal的實現原理。
在美團客戶端中,我們大量使用了這個框架。冷訊號與熱訊號的概念很容易混淆並造成一定的問題。鑑於這個問題具有一定普遍性,我將用一系列文章講解RAC中冷訊號與熱訊號的相關知識點,希望可以加深大家的理解。本文是系列文章的第一篇。
p.s. 以下程式碼和示例基於ReactiveCocoa v2.5。
什麼是冷訊號與熱訊號
冷熱訊號的概念源於.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,兩者的區別是:
- Hot Observable是主動的,儘管你並沒有訂閱事件,但是它會時刻推送,就像滑鼠移動;而Cold Observable是被動的,只有當你訂閱的時候,它才會釋出訊息。
- Hot Observable可以有多個訂閱者,是一對多,集合可以與訂閱者共享資訊;而Cold Observable只能一對一,當有不同的訂閱者,訊息是重新完整傳送。
這裡面的Observables可以理解為RACSignal。為了加深理解,我們來看這樣的幾組程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendNext:@3]; [subscriber sendCompleted]; return nil; }]; NSLog(@"Signal was created."); [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{ [signal subscribeNext:^(id x) { NSLog(@"Subscriber 1 recveive: %@", x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [signal subscribeNext:^(id x) { NSLog(@"Subscriber 2 recveive: %@", x); }]; }]; |
以上簡單地建立了一個訊號,並且依次傳送@1,@2,@3作為值。下面分別有兩個訂閱者在不同的時間段進行了訂閱,執行的結果如下:
1 2 3 4 5 6 7 |
2015-08-11 18:33:21.681 RACDemos[6505:1125196] Signal was created. 2015-08-11 18:33:21.793 RACDemos[6505:1125196] Subscriber 1 recveive: 1 2015-08-11 18:33:21.793 RACDemos[6505:1125196] Subscriber 1 recveive: 2 2015-08-11 18:33:21.793 RACDemos[6505:1125196] Subscriber 1 recveive: 3 2015-08-11 18:33:22.683 RACDemos[6505:1125196] Subscriber 2 recveive: 1 2015-08-11 18:33:22.683 RACDemos[6505:1125196] Subscriber 2 recveive: 2 2015-08-11 18:33:22.683 RACDemos[6505:1125196] Subscriber 2 recveive: 3 |
我們可以看到,訊號在18:33:21.681時被建立,18:33:21.793依次接到1、2、3三個值,而在18:33:22.683再依次接到1、2、3三個值。說明了變數名為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 |
RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [subscriber sendNext:@1]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [subscriber sendNext:@2]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@3]; }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [subscriber sendCompleted]; }]; return nil; }] publish]; [connection connect]; RACSignal *signal = connection.signal; NSLog(@"Signal was created."); [[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{ [signal subscribeNext:^(id x) { NSLog(@"Subscriber 1 recveive: %@", x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2.1 schedule:^{ [signal subscribeNext:^(id x) { NSLog(@"Subscriber 2 recveive: %@", x); }]; }]; |
稍微有些複雜,我們來一一分析:
- 建立了一個訊號,在1秒、2秒、3秒分別傳送1、2、3這三個值,4秒傳送結束訊號。
- 對這個訊號呼叫publish方法得到一個RACMulticastConnection。
- 讓connection進行連線操作。
- 獲得connection的訊號。
- 分別在1.1秒和2.1秒訂閱獲得的訊號。
拋開RACMulticastConnection是個什麼東東,我們先來看下結果:
1 2 3 4 |
2015-08-12 11:07:49.943 RACDemos[9418:1186344] Signal was created. 2015-08-12 11:07:52.088 RACDemos[9418:1186344] Subscriber 1 recveive: 2 2015-08-12 11:07:53.044 RACDemos[9418:1186344] Subscriber 1 recveive: 3 2015-08-12 11:07:53.044 RACDemos[9418:1186344] Subscriber 2 recveive: 3 |
首先告訴大家
-[RACSignal publish]、- [RACMulticastConnection connect]、- [RACMulticastConnection signal]
這幾個操作生成了一個熱訊號。
我們再來關注下輸出結果的一些細節:
- 訊號在11:07:49.943被建立
- 11:07:52.088時訂閱者1才收到2這個值,說明1這個值沒有接收到,時間間隔是2秒多
- 11:07:53.044時訂閱者1和訂閱者2同時收到3這個值,時間間隔是3秒多
參考一開始的Hot Observables的論述和兩段小程式的輸出結果,我們可以確定冷熱訊號的如下特點:
- 熱訊號是主動的,即使你沒有訂閱事件,它仍然會時刻推送。如第二個例子,訊號在50秒被建立,51秒的時候1這個值就推送出來了,但是當時還沒有訂閱者。而冷訊號是被動的,只有當你訂閱的時候,它才會傳送訊息。如第一個例子。
- 熱訊號可以有多個訂閱者,是一對多,訊號可以與訂閱者共享資訊。如第二個例子,訂閱者1和訂閱者2是共享的,他們都能在同一時間接收到3這個值。而冷訊號只能一對一,當有不同的訂閱者,訊息會從新完整傳送。如第一個例子,我們可以觀察到兩個訂閱者沒有聯絡,都是基於各自的訂閱時間開始接收訊息的。
好的,至此我們知道了什麼是冷訊號與熱訊號,瞭解了它們的特點。下一篇文章我們來看看為什麼要區分冷訊號與熱訊號。