ReactiveCocoa 談談concat,reactivecocoaconcat
今天的一個商務程序,商務程序大概就是這樣的
1.從CoreData中擷取之前的資料
2.更新介面
3.從網路擷取資料
4.判斷擷取結果
5.處理錯誤判斷
6.更新介面
7.判斷結果numberOfNews欄位
8.現實numberOfNews資訊
這種順序行的處理,正正是ReactiveCocoa的擅長解決的問題,那麼問題來了,怎麼才能通過Signal,將if else 轉換資料,要知道,很多地方都在block裡面
這就需要用到flattenMap 和 then 這兩個東西
來看看React的玩法
1 //1.從CoreData中擷取資料 2 RACSignal *local = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 3 //1.1 擷取完成後通知下一步 4 [subscriber sendNext:nil]; 5 [subscriber sendCompleted]; 6 return nil; 7 }]; 8 9 //2.轉換資料,這個過程沒有理由在mainThread中進行的10 RACSignal *viewModel = [[local subscribeOn:[RACScheduler scheduler]] map:^id(id value) {11 //1.2 將CoreDataModel轉換成視圖模型12 return nil;13 }];14 15 //3.顯示到介面中16 [viewModel subscribeNext:^(id x) {17 18 19 }];20 21 //4.建立一個網路請求22 RACSignal *request = [viewModel then:^RACSignal *{23 return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {24 25 NSURLSessionTask *task = nil;//這裡建立一個網路請求26 27 return [RACDisposable disposableWithBlock:^{28 if (task.state != NSURLSessionTaskStateCompleted) {29 [task cancel];30 }31 }];32 33 }];34 35 }];36 37 38 39 //5.避免重複請求,使用MutileConnection轉換Signal40 RACMulticastConnection *requestMutilConnection = [request multicast:[RACReplaySubject subject]];41 [requestMutilConnection connect];42 43 //6.處理伺服器結果44 RACSignal *response = [request flattenMap:^RACStream *(id value) {45 //比如response中包含一個state 的枚舉欄位,判斷這貨是返回是否有效請求46 47 // return [RACSignal return:value];48 return [RACSignal error:value];49 }];50 51 //7.更新介面52 [response subscribeNext:^(id x) {53 //再次更新介面54 }];55 56 //8.處理錯誤57 [response subscribeError:^(NSError *error) {58 //處理錯誤59 }];
當然,為了簡化,裡面留了個坑,並且省略許多邏輯代碼
回到正題,concat 是 RACSignal 的一個執行個體方法
在源碼實現如下
- (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];}
上面的代碼
1.建立一個新的訊號
2.在原來的訊號中訂閱subscribeNext 並在completed block中將建立的Signal的subscriber傳入到我們concat的訊號
這裡非常容易理解為什麼可以在上一個訊號完成時接著調用下一個訊號,原因就在 signal subscribe:subscriber這裡啊
但是事情並非這麼簡單
再看看如果使用concat 時會怎麼樣
一個非常簡單粗暴的程式碼片段
1 RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 2 3 NSLog(@"oneSignal createSignal"); 4 [subscriber sendNext:@""]; 5 [subscriber sendCompleted]; 6 7 return [RACDisposable disposableWithBlock:^{ 8 NSLog(@"oneSignal dispose"); 9 }];10 }];11 12 RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]];13 14 [connection connect];15 16 [connection.signal subscribeNext:^(id x) {17 NSLog(@"2");18 }];19 20 21 RACSignal *afterConcat = [connection.signal concat:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {22 [subscriber sendNext:@""];23 return nil;24 }]];25 26 [afterConcat subscribeNext:^(id x) {27 NSLog(@"afterConcat subscribeNext");28 }];
輸出結果
2015-10-15 23:00:26.998 conatAndThen[3814:2388477] oneSignal createSignal2015-10-15 23:00:26.999 conatAndThen[3814:2388477] oneSignal dispose2015-10-15 23:00:27.001 conatAndThen[3814:2388477] 22015-10-15 23:00:27.001 conatAndThen[3814:2388477] afterConcat subscribeNext2015-10-15 23:00:27.002 conatAndThen[3814:2388477] afterConcat subscribeNext
afterConcat 的 subscribNext被調用了兩次!!!
在來看看then
1 RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 2 3 NSLog(@"oneSignal createSignal"); 4 [subscriber sendNext:@""]; 5 [subscriber sendCompleted]; 6 7 return [RACDisposable disposableWithBlock:^{ 8 NSLog(@"oneSignal dispose"); 9 }];10 }];11 12 RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]];13 14 [connection connect];15 16 [connection.signal subscribeNext:^(id x) {17 NSLog(@"2");18 }];19 20 21 RACSignal *then = [connection.signal then:^RACSignal *{22 23 return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {24 [subscriber sendNext:@""];25 return nil;26 }];27 28 }];29 30 [then subscribeNext:^(id x) {31 NSLog(@"then subscribNext");32 }];
輸出結果
2015-10-15 23:02:40.746 conatAndThen[3848:2419019] oneSignal createSignal2015-10-15 23:02:40.747 conatAndThen[3848:2419019] oneSignal dispose2015-10-15 23:02:40.748 conatAndThen[3848:2419019] 22015-10-15 23:02:40.750 conatAndThen[3848:2419019] then subscribNext
這才是我們想要的結果
then 實際上是對 concat 的封裝
我們看看源碼是怎麼避免重複執行的
- (RACSignal *)then:(RACSignal * (^)(void))block {NSCParameterAssert(block != nil);return [[[selfignoreValues]concat:[RACSignal defer:block]]setNameWithFormat:@"[%@] -then:", self.name];}
關鍵就在ignoreValues 方法中
- (RACSignal *)ignoreValues {return [[self filter:^(id _) {return NO;}] setNameWithFormat:@"[%@] -ignoreValues", self.name];}
為了證明我的猜想,在demo中concat前filter一次
RACSignal *afterConcat = [[connection.signal filter:^BOOL(id value) { return NO; }] concat:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@""]; return nil; }]];
結果如下
2015-10-15 23:09:51.013 conatAndThen[3967:2511660] oneSignal createSignal2015-10-15 23:09:51.013 conatAndThen[3967:2511660] oneSignal dispose2015-10-15 23:09:51.015 conatAndThen[3967:2511660] 22015-10-15 23:09:51.016 conatAndThen[3967:2511660] afterConcat subscribeNext
更深入的問題來了,為什麼filter一次就可以避免重複發送
從源碼拷貝出來整理分析
RACSignal *after = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *sourceDisposable = [connection.signal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{
RACDisposable *concattedDisposable = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@""];//試試把這個注釋, after subscribeNext 只會執行一次 return nil; }] subscribe:subscriber]; serialDisposable.disposable = concattedDisposable; }]; serialDisposable.disposable = sourceDisposable; return serialDisposable; }]; [after subscribeNext:^(id x) { NSLog(@"afterConcat subscribeNext"); }];
真相已經出現了
在completed block 中 建立的signal(SA),其subsciber (A)已經變成了外層的Signal 的 subsciber,而 connection.signal 中 的subscribeNext 已經對(A)sendNext 一次,而我們需要concat 的signal 需要通知訂閱這 在SA又sendNext一次, 所以 then 的出現就是避免 [subscriber sendNext:x]對外部執行流程的影響
參考文獻
https://github.com/ReactiveCocoa/ReactiveCocoa
http://tech.meituan.com/RACSignalSubscription.html