Edited at

Swiftで複数の非同期処理の完了時に処理を行う

More than 1 year has passed since last update.


概要

複数の非同期処理の完了後に、待ち合わせて何かしたい場合のTipsです。


イメージ

非同期処理1: ----->完了

非同期処理2: ----------->完了★全ての処理が終わったココで何かしたい!

非同期処理3: -->完了


利用例


  • ある画面で複数のAPIの非同期通信処理が必要で、全てのAPIレスポンスを取得した後に画面を更新したい
    (複数種類のAPIのレスポンスデータを取得後、UITableViewをreloadData()を1回だけ呼ぶ等)

  • SDKでデータの取得処理が非同期になっており、for文で全ての複数データを取得してからデータ整形したい
    (Photos frameworkのrequestImageメソッド等)


実装例(並列)


  • 各非同期処理の開始時にstart、完了時にendを出力するプログラムです。

  • GCD(Grand Central Dispatch)のDispatchGroupを作成し、非同期処理の実行前にenter()、実行後にleave()を呼ぶことで、複数の非同期処理の開始と完了を管理します。

  • 全ての処理で完了の合図としてleave()が呼ばれた後に、notify()メソッドで指定したクロージャが実行されます。


実装例(並列)

func doMultiAsyncProcess() {

let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "queue", attributes: .concurrent)

// 5つの非同期処理を実行
for i in 1...5 {
dispatchGroup.enter()
dispatchQueue.async(group: dispatchGroup) {
[weak self] in
self?.asyncProcess(number: i) {
(number: Int) -> Void in
print("#\(number) End")
dispatchGroup.leave()
}
}
}

// 全ての非同期処理完了後にメインスレッドで処理
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!


直列実行、並列実行

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 {
dispatchGroup.enter()
dispatchQueue.async(group: dispatchGroup) {
[weak self] in
self?.asyncProcess(number: i) {
(number: Int) -> Void in
print("#\(number) End")
dispatchGroup.leave()
}
}
}

// 全ての非同期処理完了後にメインスレッドで処理
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!


github

サンプルで作成したxcodeプロジェクトはこちらです。

https://github.com/shtnkgm/MultiAsyncProcessingSample