1. 程式人生 > >RACSubscriber在訂閱中的生命週期

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 引用,就不會過早釋放了。