RACSubscriber在訂閱中的生命週期
這一篇分析過 RACScriber
的生命週期,今天發現了一個新的問題,重新分析下在耗時操作中 RACScriber
的宣告週期。
下面的完整測試用例在這裡。
先看下正常情況訊號訂閱的例子:
- (void)test1 { RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:nil]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"結束了"); }]; }]; [signal subscribeNext:^(id x) { NSLog(@"test1 -- %@", x); }]; // 列印日誌 /* 2018-11-14 18:22:26.925204+0800 TestRACSubscriber[7055:2380811] test1 -- (null) 2018-11-14 18:22:26.925453+0800 TestRACSubscriber[7055:2380811] 結束了 */ }
這樣子並沒有什麼問題,訊號正常訂閱,正常結束。
但是,一般 app 中都會使用網路請求,就會出現耗時的操作,所以,再看下耗時情況下的例子:
- (void)test2 { RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[RACScheduler mainThreadScheduler] afterDelay:0.3 schedule:^{ [subscriber sendNext:nil]; [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ NSLog(@"結束了"); }]; }]; [signal subscribeNext:^(id x) { NSLog(@"test2 -- %@", x); }]; [[RACSignal never] asynchronouslyWaitUntilCompleted:nil]; // 列印日誌 /* 2018-11-14 18:25:56.233875+0800 TestRACSubscriber[7201:2383360] test2 -- (null) 2018-11-14 18:25:56.234550+0800 TestRACSubscriber[7201:2383360] 結束了 */ }
這時,使用了延時模擬了網路請求的耗時操作,並對這個操作對應的清理物件做處理。這樣跟正常使用網路請求是一樣的。檢視結果,一切正常。
接著,對上面例子繼續改造:
- (void)test3 { RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { RACDisposable *d = [[RACScheduler mainThreadScheduler] afterDelay:0.3 schedule:^{ NSLog(@"test3 -- xxx"); }]; return [RACDisposable disposableWithBlock:^{ NSLog(@"結束了"); [d dispose]; }]; }]; [signal subscribeNext:^(id x) { NSLog(@"test3 -- %@", x); }]; [[RACSignal never] asynchronouslyWaitUntilCompleted:nil]; // 列印日誌 /* 2018-11-14 18:33:02.400545+0800 TestRACSubscriber[7474:2387962] 結束了 */ }
同樣是使用延時模擬網路請求的耗時操作,不過並沒有對結果進行傳送。這個時候,延時操作並沒有最終執行,相當於網路請求被取消了。為什麼呢?延時操作不執行,就是被清理了,證明程式碼執行了 [d dispose];
,也就是訂閱過程結束了。之前文章說過,RACSubscriber
負責訊號的分發,所以這裡 RACSubscriber
已經釋放了。為什麼會釋放呢?之前文章也說了,RACSubscriber
是作為區域性變數存在的,所以會釋放。那麼有人就要說了,第二個例子為何不釋放呢?別急,再看下例子:
- (void)test4
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACDisposable *d = [[RACScheduler mainThreadScheduler] afterDelay:0.3 schedule:^{
NSLog(@"test4 -- xxx -- %@", subscriber);
}];
return [RACDisposable disposableWithBlock:^{
NSLog(@"結束了");
[d dispose];
}];
}];
[signal subscribeNext:^(id x) {
NSLog(@"test4 -- %@", x);
}];
[[RACSignal never] asynchronouslyWaitUntilCompleted:nil];
// 列印日誌
/*
2018-11-14 18:39:51.437398+0800 TestRACSubscriber[7715:2391572] test4 -- xxx -- <RACPassthroughSubscriber: 0x600000f6f7a0>
2018-11-14 18:39:51.437689+0800 TestRACSubscriber[7715:2391572] 結束了
*/
}
延時操作又執行了。到這裡,就可以知道問題了。正常的訊號訂閱,RACSubscriber
作為區域性變數沒錯,被釋放也沒錯,但是這裡 subscriber
作為變數被延時操作的 block 引用了,所以就不會立即釋放了,只有延時正常結束才釋放,所以延時操作就能夠正常執行了。對於 app 中,也就是網路請求可以正常請求了。
其實,如果你建立訊號的時候,不通過 subscriber
做任何事情的話,那跟建立一個 never
訊號沒有什麼差別。
當然,如果按照訊號的標準建立的話,一定不會有問題,因為你必定會通過 subscriber
傳送 sendError:
或者 sendCompleted
事件,這時耗時操作中就會對 subscriber
引用,就不會過早釋放了。