第一篇文章中我們介紹了冷訊號與熱訊號的概念,前一篇文章我們也討論了為什麼要區分冷訊號與熱訊號,下面我會先為大家揭曉熱訊號的本質,再給出冷訊號轉換成熱訊號的方法。
揭示熱訊號的本質
在ReactiveCocoa中,究竟什麼才是熱訊號呢?冷訊號是比較常見的,map
一下就會得到一個冷訊號。但在RAC中,好像並沒有“hot signal”這個單獨的說法。原來在RAC的世界中,所有的熱訊號都屬於一個類——RACSubject
。接下來我們來看看究竟它為什麼這麼“神奇”。
在RAC2.5文件的框架概述中,有著這樣一段描述:
A subject, represented by the RACSubject class, is a signal that can be manually controlled.
Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals.
For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.
Some subjects offer additional behaviors as well. In particular, RACReplaySubject can be used to buffer events for future subscribers, like when a network request finishes before anything is ready to handle the result.
從這段描述中,我們可以發現Subject具備如下三個特點:
- Subject是“可變”的。
- Subject是非RAC到RAC的一個橋樑。
- Subject可以附加行為,例如
RACReplaySubject
具備為未來訂閱者緩衝事件的能力。
從第三個特點來看,Subject具備為未來訂閱者緩衝事件的能力,那也就說明它是自身是有狀態的。根據上文的介紹,Subject是符合熱訊號的特點的。為了驗證它,我們再來做個簡單實驗:
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 |
RACSubject *subject = [RACSubject subject]; RACSubject *replaySubject = [RACReplaySubject subject]; [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{ // Subscriber 1 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 1 get a next value: %@ from subject", x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 1 get a next value: %@ from replay subject", x); }]; // Subscriber 2 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 2 get a next value: %@ from subject", x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 2 get a next value: %@ from replay subject", x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [subject sendNext:@"send package 1"]; [replaySubject sendNext:@"send package 1"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{ // Subscriber 3 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 3 get a next value: %@ from subject", x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 3 get a next value: %@ from replay subject", x); }]; // Subscriber 4 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 4 get a next value: %@ from subject", x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 4 get a next value: %@ from replay subject", x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [subject sendNext:@"send package 2"]; [replaySubject sendNext:@"send package 2"]; }]; |
按照時間線來解讀一下上述程式碼:
- 0s時建立
subject
與replaySubject
這兩個subject。 - 0.1s時
Subscriber 1
分別訂閱了subject
與replaySubject
。 - 0.1s時
Subscriber 2
也分別訂閱了subject
與replaySubject
。 - 1s時分別向
subject
與replaySubject
傳送了"send package 1"
這個字串作為值。 - 1.1s時
Subscriber 3
分別訂閱了subject
與replaySubject
。 - 1.1s時
Subscriber 4
也分別訂閱了subject
與replaySubject
。 - 2s時再分別向
subject
與replaySubject
傳送了"send package 2"
這個字串作為值。
接下來看一下輸出的結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
2015-09-28 13:35:22.855 RACDemos[13646:1269269] Start 2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from subject 2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from subject 2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from replay subject 2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from replay subject 2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 1 from replay subject 2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 1 from replay subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from replay subject |
結合結果可以分析出如下內容:
- 22.855s時,測試啟動,
subject
與replaySubject
建立完畢。 - 23.856s時,距離啟動大約1s後,
Subscriber 1
和Subscriber 2
同時從subject
接收到了"send package 1"
這個值。 - 23.857s時,也是距離啟動大約1s後,
Subscriber 1
和Subscriber 2
同時從replaySubject
接收到了"send package 1"
這個值。 - 24.059s時,距離啟動大約1.2s後,
Subscriber 3
和Subscriber 4
同時從replaySubject
接收到了"send package 1"
這個值。注意Subscriber 3
和Subscriber 4
並沒有從subject
接收"send package 1"
這個值。 - 25.039s時,距離啟動大約2.1s後,
Subscriber 1
、Subscriber 2
、Subscriber 3
、Subscriber 4
同時從subject
接收到了"send package 2"
這個值。 - 25.040s時,距離啟動大約2.1s後,
Subscriber 1
、Subscriber 2
、Subscriber 3
、Subscriber 4
同時從replaySubject
接收到了"send package 2"
這個值。
只關注subject
,根據時間線,我們可以得到下圖:
經過觀察不難發現,4個訂閱者實際上是共享subject
的,一旦這個subject
傳送了值,當前的訂閱者就會同時接收到。由於Subscriber 3
與Subscriber 4
的訂閱時間稍晚,所以錯過了第一次值的傳送。這與冷訊號是截然不同的反應。冷訊號的圖類似下圖:
對比上面兩張圖,是不是可以發現,subject
類似“直播”,錯過了就不再處理。而signal
類似“點播”,每次訂閱都會從頭開始。所以我們有理由認定subject
天然就是熱訊號。
下面再來看看replaySubject
,根據時間線,我們能得到另一張圖:
將圖3與圖1對比會發現,Subscriber 3
與Subscriber 4
在訂閱後馬上接收到了“歷史值”。對於Subscriber 3
和Subscriber 4
來說,它們只關心“歷史的值”而不關心“歷史的時間線”,因為實際上1
與2
是間隔1s傳送的,但是它們接收到的顯然不是。舉個生動的例子,就好像科幻電影裡面主人公穿越時間線後會先把所有的回憶快速閃過再來到現實一樣。(見《X戰警:逆轉未來》、《蝴蝶效應》)所以我們也有理由認定replaySubject
天然也是熱訊號。
看到這裡,我們終於揭開了熱訊號的面紗,結論就是:
RACSubject
及其子類是熱訊號。RACSignal
排除RACSubject
類以外的是冷訊號。
如何將一個冷訊號轉化成熱訊號——廣播
冷訊號與熱訊號的本質區別在於是否保持狀態,冷訊號的多次訂閱是不保持狀態的,而熱訊號的多次訂閱可以保持狀態。所以一種將冷訊號轉換為熱訊號的方法就是,將冷訊號訂閱,訂閱到的每一個時間通過RACSbuject
傳送出去,其他訂閱者只訂閱這個RACSubject
。
觀察下面的程式碼:
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 |
RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"Cold signal be subscribed."); [[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{ [subscriber sendNext:@"A"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@"B"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{ [subscriber sendCompleted]; }]; return nil; }]; RACSubject *subject = [RACSubject subject]; NSLog(@"Subject created."); [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [coldSignal subscribe:subject]; }]; [subject subscribeNext:^(id x) { NSLog(@"Subscriber 1 recieve value:%@.", x); }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [subject subscribeNext:^(id x) { NSLog(@"Subscriber 2 recieve value:%@.", x); }]; |
執行順序是這樣的:
- 建立一個冷訊號:
coldSignal
。該訊號宣告瞭“訂閱後1.5秒傳送‘A’,3秒傳送’B’,5秒傳送完成事件”。 - 建立一個RACSubject:
subject
。 - 在2秒後使用這個
subject
訂閱coldSignal
。 - 立即訂閱這個
subject
。 - 4秒後訂閱這個
subject
。
如果所料不錯的話,通過訂閱這個subject
並不會引起coldSignal
重複執行block的內容。我們來看下結果:
1 2 3 4 5 |
2015-09-28 19:36:45.703 RACDemos[14110:1556061] Subject created. 2015-09-28 19:36:47.705 RACDemos[14110:1556061] Cold signal be subscribed. 2015-09-28 19:36:49.331 RACDemos[14110:1556061] Subscriber 1 recieve value:A. 2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 1 recieve value:B. 2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 2 recieve value:B. |
參考時間線,會得到下圖:
不難發現其中的幾個重點:
subject
是從一開始就建立好的,等到2s後便開始訂閱coldSignal
。Subscriber 1
是subject
建立後就開始訂閱的,但是第一個接收時間與subject
接收coldSignal
第一個值的時間是一樣的。Subscriber 2
是subject
建立4s後開始訂閱的,所以只能接收到第二個值。
通過觀察可以確定,subject
就是coldSignal
轉化的熱訊號。所以使用RACSubject
來將冷訊號轉化為熱訊號是可行的。
當然,使用這種RACSubject
來訂閱冷訊號得到熱訊號的方式仍有一些小的瑕疵。例如subject
的訂閱者提前終止了訂閱,而subject
並不能終止對coldSignal
的訂閱。(RACDisposable
是一個比較大的話題,我計劃在其他的文章中詳細闡述它,也希望感興趣的同學自己來理解。)所以在RAC庫中對於冷訊號轉化成熱訊號有如下標準的封裝:
1 2 3 4 5 |
- (RACMulticastConnection *)publish; - (RACMulticastConnection *)multicast:(RACSubject *)subject; - (RACSignal *)replay; - (RACSignal *)replayLast; - (RACSignal *)replayLazily; |
這5個方法中,最為重要的就是- (RACMulticastConnection *)multicast:(RACSubject *)subject;
這個方法了,其他幾個方法也是間接呼叫它的。我們來看看它的實現:
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 |
/// implementation RACSignal (Operations) - (RACMulticastConnection *)multicast:(RACSubject *)subject { [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name]; RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject]; return connection; } /// implementation RACMulticastConnection - (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { NSCParameterAssert(source != nil); NSCParameterAssert(subject != nil); self = [super init]; if (self == nil) return nil; _sourceSignal = source; _serialDisposable = [[RACSerialDisposable alloc] init]; _signal = subject; return self; } #pragma mark Connecting - (RACDisposable *)connect { BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); if (shouldConnect) { self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; } return self.serialDisposable; } - (RACSignal *)autoconnect { __block volatile int32_t subscriberCount = 0; return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { OSAtomicIncrement32Barrier(&subscriberCount); RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; RACDisposable *connectionDisposable = [self connect]; return [RACDisposable disposableWithBlock:^{ [subscriptionDisposable dispose]; if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { [connectionDisposable dispose]; } }]; }] setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; } |
雖然程式碼比較短但不是很好懂,大概來說明一下:
- 當
RACSignal
類的例項呼叫- (RACMulticastConnection *)multicast:(RACSubject *)subject
時,以self
和subject
作為構造引數建立一個RACMulticastConnection
例項。 RACMulticastConnection
構造的時候,儲存source
和subject
作為成員變數,建立一個RACSerialDisposable
物件,用於取消訂閱。- 當
RACMulticastConnection
類的例項呼叫- (RACDisposable *)connect
這個方法的時候,判斷是否是第一次。如果是的話用_signal
這個成員變數來訂閱sourceSignal
之後返回self.serialDisposable
;否則直接返回self.serialDisposable
。這裡面訂閱sourceSignal
是重點。 RACMulticastConnection
的signal
只讀屬性,就是一個熱訊號,訂閱這個熱訊號就避免了各種副作用的問題。它會在- (RACDisposable *)connect
第一次呼叫後,根據sourceSignal
的訂閱結果來傳遞事件。- 想要確保第一次訂閱就能成功訂閱
sourceSignal
,可以使用- (RACSignal *)autoconnect
這個方法,它保證了第一個訂閱者觸發sourceSignal
的訂閱,也保證了當返回的訊號所有訂閱者都關閉連線後sourceSignal
被正確關閉連線。
由於RAC是一個執行緒安全的框架,所以好奇的同學可以瞭解下“OSAtomic*”這一系列的原子操作。拋開這些應該不難理解上述程式碼。
瞭解原始碼之後,這個方法的正確使用就清楚了,應該像這樣:
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 |
RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"Cold signal be subscribed."); [[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{ [subscriber sendNext:@"A"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@"B"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{ [subscriber sendCompleted]; }]; return nil; }]; RACSubject *subject = [RACSubject subject]; NSLog(@"Subject created."); RACMulticastConnection *multicastConnection = [coldSignal multicast:subject]; RACSignal *hotSignal = multicastConnection.signal; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [multicastConnection connect]; }]; [hotSignal subscribeNext:^(id x) { NSLog(@"Subscribe 1 recieve value:%@.", x); }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [hotSignal subscribeNext:^(id x) { NSLog(@"Subscribe 2 recieve value:%@.", x); }]; }]; |
或者這樣:
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 |
RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"Cold signal be subscribed."); [[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{ [subscriber sendNext:@"A"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@"B"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{ [subscriber sendCompleted]; }]; return nil; }]; RACSubject *subject = [RACSubject subject]; NSLog(@"Subject created."); RACMulticastConnection *multicastConnection = [coldSignal multicast:subject]; RACSignal *hotSignal = multicastConnection.autoconnect; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [hotSignal subscribeNext:^(id x) { NSLog(@"Subscribe 1 recieve value:%@.", x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [hotSignal subscribeNext:^(id x) { NSLog(@"Subscribe 2 recieve value:%@.", x); }]; }]; |
以上的兩種寫法和之前用Subject來傳遞的例子都可以得到相同的結果。
下面再來看看其他幾個方法的實現:
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 |
/// implementation RACSignal (Operations) - (RACMulticastConnection *)publish { RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name]; RACMulticastConnection *connection = [self multicast:subject]; return connection; } - (RACSignal *)replay { RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name]; RACMulticastConnection *connection = [self multicast:subject]; [connection connect]; return connection.signal; } - (RACSignal *)replayLast { RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name]; RACMulticastConnection *connection = [self multicast:subject]; [connection connect]; return connection.signal; } - (RACSignal *)replayLazily { RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]]; return [[RACSignal defer:^{ [connection connect]; return connection.signal; }] setNameWithFormat:@"[%@] -replayLazily", self.name]; } |
這幾個方法的實現都相當簡單,只是為了簡化而封裝,具體說明一下:
- (RACMulticastConnection *)publish
就是幫忙建立了RACSubject
。- (RACSignal *)replay
就是用RACReplaySubject
來作為subject
,並立即執行connect
操作,返回connection.signal
。其作用是上面提到的replay
功能,即後來的訂閱者可以收到歷史值。- (RACSignal *)replayLast
就是用Capacity
為1的RACReplaySubject
來替換- (RACSignal *)replay
的`subject。其作用是使後來訂閱者只收到最後的歷史值。- (RACSignal *)replayLazily
和- (RACSignal *)replay
的區別就是replayLazily
會在第一次訂閱的時候才訂閱sourceSignal
。
所以,其實本質仍然是
使用一個Subject來訂閱原始訊號,並讓其他訂閱者訂閱這個Subject,這個Subject就是熱訊號。
現在再回過來看下之前系列文章第二篇中那個業務場景的例子,其實修改的方法很簡單,就是在網路獲取的fetchData
這個訊號後面,增加一個replayLazily
變換,就不會出現網路請求重發6次的問題了。
修改後的程式碼如下,大家可以試試:
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 |
self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]]; self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; @weakify(self) RACSignal *fetchData = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { @strongify(self) NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id responseObject) { [subscriber sendNext:responseObject]; [subscriber sendCompleted]; } failure:^(NSURLSessionDataTask *task, NSError *error) { [subscriber sendError:error]; }]; return [RACDisposable disposableWithBlock:^{ if (task.state != NSURLSessionTaskStateCompleted) { [task cancel]; } }]; }] replayLazily]; // modify here!! RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) { if ([value[@"title"] isKindOfClass:[NSString class]]) { return [RACSignal return:value[@"title"]]; } else { return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]]; } }]; RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) { if ([value[@"desc"] isKindOfClass:[NSString class]]) { return [RACSignal return:value[@"desc"]]; } else { return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]]; } }]; RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) { NSError *error = nil; RenderManager *renderManager = [[RenderManager alloc] init]; NSAttributedString *rendered = [renderManager renderText:value error:&error]; if (error) { return [RACSignal error:error]; } else { return [RACSignal return:rendered]; } }]; RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."]; RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."]; RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]]; [[RACSignal merge:@[title, desc, renderedDesc]] subscribeError:^(NSError *error) { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:error.domain delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; }]; |
當然,細心的同學會發現這樣修改,仍然有許多計算上的浪費,例如將fetchData
轉換為title
的block會執行多次,將fetchData
轉換為desc
的block也會執行多次。但是由於這些block都是無副作用的,計算量並不大,可以忽略不計。如果計算量大的,也需要對中間的訊號進行熱訊號的轉換。不過請不要忽略冷熱訊號的轉換本身也是有計算代價的。
好的,寫到這裡,我們終於揭開RAC中冷訊號與熱訊號的全部面紗,也知道如何使用了。希望這個系列文章可以讓大家更好地瞭解RAC,避免使用RAC遇到的誤區。謝謝大家。
美團iOS組有很多志同道合的小夥伴,對於各種技術都有著深入的瞭解,我們熱忱地歡迎一切牛掰的小夥伴加入,共同學習,共同進步。(簡歷請傳送到郵箱 liangsi02@meituan.com)