概要
複数の非同期処理の完了後に、待ち合わせて何かしたい場合のTipsです。
イメージ
非同期処理1: ----->完了
非同期処理2: ----------->完了★全ての処理が終わったココで何かしたい!
非同期処理3: -->完了
利用例
- ある画面で複数のAPIの非同期通信処理が必要で、全てのAPIレスポンスを取得した後に画面を更新したい
(複数種類のAPIのレスポンスデータを取得後、UITableViewをreloadData()を1回だけ呼ぶ等) - SDKでデータの取得処理が非同期になっており、for文で全ての複数データを取得してからデータ整形したい
(Photos frameworkのrequestImageメソッド等)
実装例(並列①)
- 各非同期処理の開始時にstart、完了時にendを出力するプログラムです。
- GCD(Grand Central Dispatch)のDispatchGroupを作成し、
async(group:qos:flags:execute:)
メソッドの引数にgroupを指定することで複数の非同期処理の開始と完了を管理します。 - 全ての処理が完了した時に
notify()
メソッドで指定したクロージャが実行されます。
実装例(並列)
func doMultiAsyncProcess() {
let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "queue", attributes: .concurrent)
// 5つの非同期処理を実行
for i in 1...5 {
dispatchQueue.async(group: dispatchGroup) {
[weak self] in
self?.asyncProcess(number: i) {
(number: Int) -> Void in
print("#\(number) End")
}
}
}
// 全ての非同期処理完了後にメインスレッドで処理
dispatchGroup.notify(queue: .main) {
print("All Process Done!")
}
}
// 非同期処理
func asyncProcess(number: Int, completion: (_ number: Int) -> Void) {
print("#\(number) Start")
sleep((arc4random() % 100 + 1) / 100)
completion(number)
}
実行結果(並列)
各タスクが別のタスクを待たずに並列かつ非同期で実行され、最後にprint("All Process Done!")
が実行されます。
実行結果(並列)
#1 Start
#3 Start
#4 Start
#2 Start
#5 Start
#3 End
#1 End
#4 End
#2 End
#5 End
All Process Done!
実装例(並列②)
- 複数の非同期処理を並列実行してすべての完了を待つ処理の別の実装方法です。
- この方法は各非同期処理が別のDispatchQueueで処理を実行している場合に有効です。
- GCD(Grand Central Dispatch)のDispatchGroupを作成し、非同期処理の実行前に
enter()
、実行後にleave()
を呼ぶことで、複数の非同期処理の開始と完了を管理します。 - 全ての処理で完了の合図として
leave()
が呼ばれた後に、notify()
メソッドで指定したクロージャが実行されます。
実装例(並列)
func doMultiAsyncProcess() {
let dispatchGroup = DispatchGroup()
// 5つの非同期処理を実行
for i in 1...5 {
dispatchGroup.enter()
asyncProcess(number: i) { (number: Int) -> Void in
defer { dispatchGroup.leave() }
print("#\(number) End")
}
}
// 全ての非同期処理完了後にメインスレッドで処理
dispatchGroup.notify(queue: .main) {
print("All Process Done!")
}
}
// 非同期処理
func asyncProcess(number: Int, completion: @escaping (_ number: Int) -> Void) {
print("#\(number) Start")
let interval = TimeInterval(arc4random() % 100 + 1) / 100
DispatchQueue.global().asyncAfter(deadline: .now() + interval) {
completion(number)
}
}
直列実行、並列実行
GCDのキューは大きく分けて直列と並列の2種類があります。
イメージ
# 並列
非同期処理1: ---->
非同期処理2: -------->★全て完了
非同期処理3: -->
# 直列
非同期処理1: ---->
非同期処理2: -------->
非同期処理3: -->★全て完了
- 直列キュー(シリアルキュー)の実行スレッドは1つで、同時に実行できるタスクは1つです。
- 並列キュー(コンカレントキュー)の実行スレッドは複数で、同時に実行できるタスクは複数です。
直列にタスクを実行するキューを作成するには、attributeの.concurrent
の指定を外します。
直列、並列キューの指定方法
// 並列キュー / attribute指定あり(.concurrent)
let dispatchQueue = DispatchQueue(label: "queue", attributes: .concurrent)
// 直列キュー / attibutes指定なし
let dispatchQueue = DispatchQueue(label: "queue")
独自にキューを作成するのでなく、システムで用意されているグローバルキューを利用する場合は.concurrentを指定しなくても並列キューとなるため、注意が必要です。
グローバルキュー
// 並列キュー(処理の優先度はdefault)
let dispatchQueue = DispatchQueue.global(qos: .default)
実装例(直列)
実装例(直列)
func doMultiAsyncProcess() {
let dispatchGroup = DispatchGroup()
// 直列キュー / attibutes指定なし
let dispatchQueue = DispatchQueue(label: "queue")
// 5つの非同期処理を実行
for i in 1...5 {
dispatchQueue.async(group: dispatchGroup) {
[weak self] in
self?.asyncProcess(number: i) {
(number: Int) -> Void in
print("#\(number) End")
}
}
}
// 全ての非同期処理完了後にメインスレッドで処理
dispatchGroup.notify(queue: .main) {
print("All Process Done!")
}
}
実行結果(直列)
全てのタスクが逐次的(直列)に実行されているのが分かります。
実行結果(直列)
#1 Start
#1 End
#2 Start
#2 End
#3 Start
#3 End
#4 Start
#4 End
#5 Start
#5 End
All Process Done!