dispatch_semaphore網路應用

JoyZhang發表於2018-01-31

前段時間學習了一下GCD相關的知識,看了不少同學寫的Blog,通過使用dispatch_groupdispatch_semaphore解決網路請求同步、非同步問題,讓我受益良多,也激發了我的思考。

我提出了一種問題場景

多請求非同步發起,要求其回撥按順序執行。

題目很簡單,為了能方便大家的理解,我舉個栗子:A、B、C三個網路請求,一同發出,當然它們請求回來的時間肯定是不一致的,但是我希望三個網路請求對應的blockA、blockB、blockC是按照順序執行的。
也就是說就算網路請求B回來了,但是A沒回來,blockB也要等著A請求的blockA先執行完才能執行。

我想我的問題已經描述的很清楚了,不知道讀到這裡同學們有沒有想法?

可以思考一下,當然!其實你使用各種識別符號控制、i++計量、請求任務的Block中巢狀另一個請求任務的發起動作等……方法是可以解決這種場景問題。但是!既然是為了學習,就不要把為了規避問題當做第一要務。

接下來我說一下我的處理辦法,可能不是最好的辦法,也就是為大家提供一個思路。

1. 首先我們模擬一下網路請求

此處模擬網路求情寫的有點複雜了,之所以加到並行佇列中是想體現這三個網路請求是非同步併發的。

    dispatch_queue_t requestQueue = dispatch_queue_create("com.requestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(requestQueue, ^{
    
    	// 模擬第一個網路請求requestA,4s後返回
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        	// 執行requestA請求完成後的taskA
        });
    });
    
    dispatch_async(requestQueue, ^{
    	// 模擬第一個網路請求requestB,2s後返回
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        	// 執行requestB請求完成後的taskB
        });
    });
    
    dispatch_async(requestQueue, ^{
    	// 模擬第一個網路請求requestA,3s後返回
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        	// 執行requestC請求完成後的taskC
        });
    });

複製程式碼

2. 保證順序執行回撥

為了能順序執行回撥方法,我想到了序列佇列,如果我把blockA/B/C都丟到一個序列佇列中,那麼就可以保證回撥處理是順序執行的了,所以有了下面的程式碼:

dispatch_queue_t blockQueue = dispatch_queue_create("com.blockQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(blockQueue, ^{
        [self taskA];
    });
    dispatch_sync(blockQueue, ^{
        [self taskB];
    });
    dispatch_sync(blockQueue, ^{
        [self taskC];
    });
複製程式碼

我將請求後需要執行的task預先加入到了一個序列佇列中。

3. 控制啟動的時機

任何一個task的執行都有兩個條件約束:

  1. 已執行優先順序更高的task
  2. task本身對應的request已請求完成;

既然序列佇列保證了第1點:我們的執行順序,我們在加上第二點約束即可。

我選擇了使用訊號量來通知請求是否完成,並能夠使未完成的請求所對應的task保持等待狀態!簡直完美。

建立了3個訊號量的例項

初始化訊號量為0。

@property (nonatomic, strong) dispatch_semaphore_t semaA;
@property (nonatomic, strong) dispatch_semaphore_t semaB;
@property (nonatomic, strong) dispatch_semaphore_t semaC;
複製程式碼

修改上面虛擬網路請求的程式碼

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t requestQueue = dispatch_queue_create("com.requestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(requestQueue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            // 通過訊號量通知
            // 執行requestA請求完成後的TaskA
            dispatch_semaphore_signal(self.semaA);
        });
    });
    
    dispatch_async(requestQueue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(self.semaB);
        });
    });
    
    dispatch_async(requestQueue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(self.semaC);
        });
    });
    
    // 這裡注意一下!
    // 如果不新建一個執行緒,而直接將上方的 `保證順序執行回撥` 程式碼放在這裡
    // 序列佇列會將自己佇列中的任務放在主執行緒中執行,從而造成主執行緒阻塞(假死狀態) 
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(taskQueue) object:nil];
    [thread start];
}

- (void)taskQueue {
/ *

    這裡註釋掉是因為有同學提示:在我們新建的執行緒中直接呼叫taskA/B/C,效果一樣,
    想了一下,確實如此!作以修改
    dispatch_queue_t taskQueue = dispatch_queue_create("com. taskQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(taskQueue, ^{
        [self taskA];
    });
    dispatch_sync(taskQueue, ^{
        [self taskB];
    });
    dispatch_sync(taskQueue, ^{
        [self taskC];
    });
*/
    [self taskA];
    [self taskB];
    [self taskC];
}
複製程式碼

另附上- taskA/B/C的程式碼,在等待訊號量後,列印很簡單的一段log日誌:

- (void)taskA {
    
    // NSLog(@"%@", [NSThread currentThread]);
    dispatch_semaphore_wait(self.semaA, DISPATCH_TIME_FOREVER);
    
    NSLog(@"A Done");
}

- (void)taskB {
    
    dispatch_semaphore_wait(self.semaB, DISPATCH_TIME_FOREVER);
    
    NSLog(@"B Done");
}

- (void)taskC {
    
    dispatch_semaphore_wait(self.semaC, DISPATCH_TIME_FOREVER);
    
    NSLog(@"C Done");
}
複製程式碼

效果如下:

gcd_1.png

至此,就完成了設想場景的解決方案,有同學想到更好的解決方案,懇請不吝賜教!

最後想感謝一下 @詩雨 在群裡的點撥,URLSession的方案由於自己對這方面的知識不熟悉,日後學習完成再做補充哈哈哈。

相關文章