最近接到一個需求,在載入一個畫面時要同時呼叫兩支API,分別得到result之後再將兩組資料二合為一。

這邊先不討論為什麼backend不能乾淨俐落的用一支API回傳就好,正所謂無風不起浪,事出必有因,總之要怎麼在確認兩支API都得到result之後再做後續的處理呢?

你可以先call一支API然後把另外一支包在裡面,如果有三支,就再包一支,最後程式碼看起來會長這樣:

{{{{{}}}}}

不知不覺寫了一段俄羅斯娃娃呢

 

優雅的擁抱GCD

GCD裡面提供的Dispatch Group可以在多個Dispatch Queue執行完之後,透過dispatch_group_notify來獲得通知

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    NSLog(@"Queue One");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"Queue Two");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"Queue Three");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"Queues Done");
});

但若你的任務是一個非同步的網路請求呢?若將async的API request包在dispatch_group_async中,你會發現在API還沒接到result時dispatch_group_notify就接到通知了,這時咱先別急著把巢狀怪物搬回來交差了事,Dispatch Group還提供了兩個法寶:dispatch_group_enter與dispatch_group_leave

dispatch_group_enter & dispatch_group_leave

dispatch_group_enter這個方法用來計算group中的任務數,也就是說呼叫一次任務數就會+1,而dispatch_group_leave則相對的對任務數-1,因此在每個API request發出時下dispatch_group_enter,並在得到回傳結果時下dispatch_group_leave,當任務數為0時才會收到通知,感覺有點像ARC的retain count的概念。

因此就可以寫成:

Objc

dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    @weakify(self);
    [API request1: ^(APIStatus status, NSArray *result) {
        NSLog(@"Group1");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [API request2:^(APIStatus status, NSArray *result) {
        NSLog(@"Group2");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"Done");
    });

Swift

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()
API.request1 {
    print("group1")
    dispatchGroup.leave()
}

dispatchGroup.enter()
API.request2 {
    print("group2")
    dispatchGroup.leave()
}

dispatchGroup.notify(queue: .main) {
    print("Done")
}

這樣是不是比俄羅斯娃娃般的巢狀連環摳優雅多了呢~

參考資料:iOS 多线程GCD之dispatch_group