【Swift】DispatchQueueの使い方!GCDで非同期と遅延処理

この記事からわかること
- SwiftのDispatchQueueとは?
- DispatchQueueを使った非同期処理
- GCD(Grand Central Dispatch)とは?
- mainQueueとglobalQueueの違い
- 一定時間後に遅延処理を実行させる方法の意味
- Private Queueの自作方法
index
[open]
\ アプリをリリースしました /
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
Swiftで非同期処理を実装できるDispatchQueue
クラスについてまとめていきます。
GCD(Grand Central Dispatch)とは?
SwiftのDispatchQueue
クラスを知るためにはまずGCD(Grand Central Dispatch)への理解が必要になります。GCDとはmacOS、iOS、watchOS、tvOSのマルチコアハードウェアにおいて配列でタスクを実行させるための技術の1つです。
Swiftの前身であるObjective-Cから使用可能なスレッド管理手法でSwiftではDispatchフレームワークとして提供されています。その中に組み込まれているクラスの1つがDispatchQueue
クラスです。
GCDではキューにタスクを溜めていき、処理タイミングを同期的または非同期的に実行させることが可能です。
DispatchQueueクラスとは?
DispatchQueue
クラスは実際にプログラムの処理(タスク)をスレッドを分けて実行、管理することができるクラスです。通常の処理から実行タイミングをずらした非同期的な処理の実装も可能となっています。
class DispatchQueue : DispatchObject
「スレッド」とはある処理を行う命令の流れのことを指します。DispatchQueue
クラスでは「メインスレッド」または「バックグラウンドスレッド」のうち適したスレッドを使い分けて使用することができます。これにより異なる処理を同時に実行したり(並列)、連続で実行したり(直列)といったスレッド単位の管理が可能になっています。
「Queue(キュー)」なので基本的にはタスクが順番に溜まっていき、FIFO(First In First Out:先入先出)方式で実行されていきます。
キューの種類
DispatchQueue
クラスにはキューが2種類存在します。
mainQueue
メインスレッドでタスクが実行されていく直列のキュー。UI部分はこのキューで管理されているので処理後にUIを更新したい場合はこのキューに格納していく。
globalQueue
システム全体で共有される並列のキュー。5種類の実行優先度ごとにキューが管理される。
使い方
Dispatchはフレームワークなのでimport
してから使用していきます。SwiftUIを使用している場合は既に組み込み済み?のようなのでimport
しなくても正常に動作しました。
import Dispatch
DispatchQueue
クラスを使用してキューを操作するにはインスタンス化してプロパティもしくはメソッドから参照します。
mainQueue
mainQueueへはmain
プロパティを使って参照します。
class var main: DispatchQueue { get }
// 使用例
DispatchQueue.main
globalQueue
globalQueueへはglobal
メソッドを使って参照します。
class func global(qos: DispatchQoS.QoSClass = .default) -> DispatchQueue
// 使用例
DispatchQueue.global()
同期処理(sync)と非同期処理(async)
キューの中に溜まっているタスクを処理するタイミングには同期(Synchronous:シンクロナス)と非同期(Asynchronous:アシンクロナス)があります。
同期処理とは通常の処理の流れに沿ったタイミングで実行される処理タイミングのこと。非同期処理とは通常の処理の流れとはズレたタイミングで実行される処理タイミングのことです。
重たい処理や時間のかかる処理などを実行する際、非同期的に処理を行なうことで後続の処理の実行を早めることができるようになります。
DispatchQueue
クラスでは同期的に処理を実行させるためのsync
メソッドと非同期的に処理を実行させるためのasync
メソッドが用意されています。
mainQueueの中にタスクを溜めて非同期処理する
実際にキューの中にタスクを溜めて実行する様子を見てみます。まずは直列のキューであるmainQueueに処理を溜めて実行していきます。mainQueueにはmain
プロパティで参照し、async
メソッドでタスクを送信すると非同期で処理されるようになります。非同期処理として実装した部分はその処理の完了を待たずに次の処理が実行されます。
main
は直列キューなので非同期処理{}内では順番にタスクが溜まり、順番に処理されていきますがその処理に時間がかかる場合は先に後続の処理(以下の場合Endが出力)が実行されます。
DispatchQueue.main.async {
print("Hello World!")
print("こんにちは世界!")
}
print("End")
// End
// Hello World!
// こんにちは世界!
// async {}内で実行する処理速度によって順番は異なる
実行環境によるので必ずしも上記の順番になるわけではありません。async{}
内の処理が早く終われば先にそちらが出力されます。
mainQueueではsyncメソッドは使えない
mainQueueではsync
メソッドを使用することができません。コード補完では問題なく出てきてしまいますが、実行させると「Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102b45a14)」といったエラーが起きてしまいます。
DispatchQueue.main.sync {
print("Hello World!")
}
print("End")
// エラー:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102b45a14)
mainQueueキューでsync
メソッドを実行するとキューはタスクの完了を待機しますが、タスクが開始できない(デッドロックが起きる)ため終了できなくなってしまいます。
globalQueueの中にタスクを溜めて処理する
globalQueue
にはglobal
メソッドを使って参照します。main
ではプロパティでしたがメソッドなのでglobal()
となるので注意してください。
タスクを送信するのはsync
メソッドとasync
メソッドの両方が使用可能です。sync
メソッドの場合は同期的に処理が実行されます。
DispatchQueue.global().sync {
print("Hello World!")
print("こんにちは世界!")
}
print("End")
// Hello World!
// こんにちは世界!
// End
ですが並列キューなのでキューに溜まったタスクの処理順序は一定ではありません。時間のかかる処理であれば必然的に1番最後に終了し、時間のかからない処理から終了していきます。
上記の例で言うと「Hello World!」と「こんにちは世界!」は処理が早く終わる方から出力されその後に「End」が出力されます。
非同期処理を実装する
非同期処理を実装する際はasync
メソッドを使用します。
DispatchQueue.global().async {
print("Hello World!")
print("こんにちは世界!")
}
print("End")
// End
// Hello World!
// こんにちは世界!
// async {}内で実行する処理速度によって順番は異なる
Qos:実行優先度
globalQueue
の引数にはQoS(Quality of Service)と呼ばれる実行優先度を設定することができます。
QoSはglobal
メソッドの引数qos
にDispatchQoS
構造体のプロパティで指定します。
struct DispatchQoS {
static let userInteractive: DispatchQoS // 優先度:1番
static let userInitiated: DispatchQoS // 優先度:2番
static let `default`: DispatchQoS // 優先度:3番
static let utility: DispatchQoS // 優先度:4番
static let background: DispatchQoS // 優先度:5番
static let unspecified: DispatchQoS // 優先度なし
}
DispatchQoS構造体のプロパティ
userInteractive
ユーザーからの入力をインタラクティブ(対話的)に反映させるために即座に実行させる必要がある場合に指定する優先度
userInitiated
ユーザーからの入力を受けてから反映させる場合に指定する優先度
default
優先度が指定されなかった場合のデフォルト値となる優先度
明示的に指定するのは非推奨らしい
utility
ユーザーが正確性を求めていないような処理を行う場合の優先度
background
バックグラウンドで行うような処理を行う場合の優先度
let globalQ = DispatchQueue.global(qos: .background)
globalQ.async {
print("Hello World!")
print("こんにちは世界!")
}
一定時間後に処理を実行させる方法
asyncAfter
メソッドを使用すれば一定時間後に任意の処理を実行させることが可能(遅延処理)です。
遅延させたい時間は引数deadline
にDispatchTime
型の値を指定します。
func asyncAfter(
deadline: DispatchTime,
execute: DispatchWorkItem
)
DispatchTime.now()
で現在の時間を取得できるので遅延させたい秒数を+
すればその秒数分遅延させて処理を実行することができます。
DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + 1) {
// 非同期で1秒後に実行される
print("Hello World!")
print("こんにちは世界!")
}
print("End")
ミリ秒、ナノ秒、マイクロ秒で遅延させる
遅延させる時間をミリ秒、ナノ秒、マイクロ秒で指定したい場合は列挙型DispatchTImeInterval
を使用します。
let interval = DispatchTImeInterval.milliseconds(1000)
DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + interval) {
}
指定できるのは以下のとおりです。
enum DispatchTimeInterval {
case seconds(Int) // 秒数
case milliseconds(Int) // ミリ秒数
case microseconds(Int) // マイクロ秒数
case nanoseconds(Int) // ナノ秒数
case never // 間隔なし
}
Private Queueを自作して使用する
使用できるキューはメインキュー1つと5つの優先度のグローバルキューですが、新しくオリジナルのキュー(Private Queue)を作成することもできます。
Private QueueはDispatchQueue
クラスのイニシャライザを使用して作成します。
convenience init(
label: String,
qos: DispatchQoS = .unspecified,
attributes: DispatchQueue.Attributes = [],
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
target: DispatchQueue? = nil
)
引数label
にキューの名称を指定します。公式からは逆引きのDNS(Domain Name System)が推奨されています。
DispatchQueue(label: "com.amefure.queue").sync {
print("Hello World!")
print("こんにちは世界!")
}
print("End")
もちろん実行優先度を指定したり、非同期処理を実行させることも可能です。
DispatchQueue(label: "com.amefure.queue",qos: .background).async {
print("Hello World!")
print("こんにちは世界!")
}
print("End")
DispatchQueueを使用している実装例がこちらの記事にありますので興味があればご覧ください。
複数の非同期処理の完了を待機する
SwiftのDispatchQueue
で非同期処理を実装する際に複数の処理を完了まで待ってから特定の処理を行うにはDispatchGroup
を使用します。詳細な実装方法は以下の記事を参考にしてください。
同一に実行する非同期処理の数を制御する
同一に実行する非同期処理の数を制御するにはDispatchSemaphore
を使用します。詳細な実装方法は以下の記事を参考にしてください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。
私がSwift UI学習に使用した参考書