[Swift 3] Swift 3時代のGCDの基本的な使い方
はじめに
こんにちは!モバイルアプリサービス部の加藤潤です。
今回はSwift3で大きく変わったGCD(Grand Central Dispatch)のAPIについて、その基本的な使い方を見ていきたいと思います。
基礎知識のおさらい
GCDの具体的な使い方に入る前に基礎知識のおさらいをしておきます。
同期と非同期
まずは同期と非同期についてです。
GCDではキュー(queue)という「処理の入れ物」に対して処理を登録することで、適切なスレッド上で処理が行われるのですが、キューに登録する際に以下の実行方式のいずれかを指定することができます。
- 同期
- キューに処理を登録したスレッドが登録した処理が完了するのを「待つ」
- GCDのメソッドは
sync
〜
- 非同期
- キューに処理を登録したスレッドが登録した処理が完了するのを「待たない」
- GCDのメソッドは
async
〜
上記のように、違いは処理が完了するのを待つか待たないかです。
※本記事中のコード例は全てasync
にしています。
sync
は使い方を間違えるとスレッドのデッドロックを起こす可能性があります。どうしてもsync
にする必要がない限り、基本的にasync
を使うことをオススメします。
キューにも種類がある
キューにも以下の種類があります。
- Serial
- 処理を直列に実行する。キュー内の前の処理が完了してから次の処理が開始する。一度に実行される処理は一つのみ。
- Concurrent
- 処理を並列に実行する。処理がキューから取り出される順番はFIFOだが、一度に複数の処理が実行され、処理が完了する順番もバラバラ。
特別なキューであるメインキュー
キューの中でもアプリ起動時にシステムによって自動的に作られるメインキュー(main queue)と呼ばれる特別なキューがあります。
このキューに登録した処理はメインスレッド上で直列に実行されます。特別なSerialキューであるとも言えます。
サブスレッドで何か重い処理をして終わったらメインスレッドに戻す
さて、ここからGCDの具体的な使い方を見ていきましょう。
iOSアプリでは負荷の高い処理やいつ終わるかわからない通信等の処理は基本的にメインスレッドでは行いません。 メインスレッドで行うとアプリが固まりユーザーの操作を妨げてしまうためです。 そこでよくあるパターンは、サブスレッドで何か重い処理をして終わったらメインスレッドに戻すというパターンです。
自分でキューを生成
以下は自分でキューを生成して任意の処理をサブスレッドで実行、終わったらメインスレッドで処理を実行する例です。
// キューを生成してサブスレッドで実行 DispatchQueue(label: "jp.classmethod.app.queue").async { self.doSomething() // メインスレッドで実行 DispatchQueue.main.async { self.doSomething() } }
ラベル名を指定してキューを生成しています。この時生成されるのは「Serial」キューです。
Concurrentキューを生成したい場合は以下のように.concurrent
オプションを指定します。
DispatchQueue(label: "jp.classmethod.app.queue", attributes: .concurrent)
システムで用意されたキューを使う
上記で自分でキューを生成
と書いた理由は以下のようにシステムで用意されたキューを使うことも可能だからです。
// システムで用意されたキューを使う DispatchQueue.global(qos: .default).async { self.doSomething() }
DispatchQueue.global
で取得できるキューはシステムで用意された「Concurrent」キューです。
引数で指定しているqos
はQuality of Service
の略で、処理の優先度を決定するものです。ここではdefault
を指定していますが、優先度の高いものから順に以下の選択肢が用意されています。
userInteractive
userInitiated
default
utility
background
unspecified
指定した秒数後に処理を実行する
以下は5秒後に任意の処理を実行する例です。
DispatchTime.now()
で取得した現在時間に対し、.seconds(5)
で5秒足しています。seconds
はDispatchTimeInterval
というenumであり、他にmilliseconds
(ミリ秒)、microseconds
(マイクロ秒)、nanoseconds
(ナノ秒)を指定することができます。
// 5秒後に実行 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(5)) { self.doSomething() }
全てのキューの処理が終わったら任意の処理を行う
以下はキューが複数ある場合に、全てのキューの処理が終わったら任意の処理を行う例です。 最初にグループとキューを生成し、キューに処理を登録する際にグループを指定することでキューとグループを紐付けています。 全てのキューの処理が終わったらメインスレッド上で処理が行われます。
// グループを生成 let group = DispatchGroup() let queue1 = DispatchQueue(label: "jp.classmethod.app.queue1") let queue2 = DispatchQueue(label: "jp.classmethod.app.queue2") let queue3 = DispatchQueue(label: "jp.classmethod.app.queue3") // キューとグループを紐付ける queue1.async(group: group) { print("queue1 done.") } queue2.async(group: group) { print("queue2 done.") } queue3.async(group: group) { print("queue3 done.") } // タスクが全て完了したらメインスレッド上で処理を実行する group.notify(queue: DispatchQueue.main) { print("all task done.") }
DispatchWorkItemを使って処理を行う
DispatchWorkItem
はタスクをカプセル化したクラスです。このクラスを使うと以下のようにキューの指定が必須ではなくなります。
この場合は現在のコンテキスト(スレッド)上で処理が行われるようです。
// キューを指定しなくても処理が行える let workItem = DispatchWorkItem { self.doSomething() } workItem.perform()
もちろん、以下のようにキューを指定することも可能です。
queue1.async(execute: workItem)
このDispatchWorkItem
を使うと、処理の実行perform()
の他にも待機wait()
やキャンセルcancel()
など、タスクに関しての制御を細かく行うことができます。
また、DispatchWorkItemFlags
というオプションも用意されていて、QoSの細かい挙動等を調整することができます。
dispatch_onceが使えなくなった
Objective-Cの時からシングルトンパターンを実現するために使われていたdispatch_once
はSwift 3で使えなくなりました。
パラメーターであるdispatch_once_t
も使えなくなっており、実際に以下のように使おうとしたところ、
'dispatch_once_t' is unavailable in Swift: Use lazily initialized globals instead
とエラーになりました。
let token: dispatch_once_t // 'dispatch_once_t' is unavailable in Swift: Use lazily initialized globals instead dispatch_once(token) { }
dispatch_once
がSwift 3で使えないことは、Migrating to Swift 2.3 or Swift 3 from Swift 2.2においても述べられています。
Swift 3でシングルトンパターンを実現するには
dispatch_once
が使えなくなったSwift 3ではシングルトンパターンを以下のようにstatic let
プロパティで実現します。
この書き方自体は以前から使えており、Swift 3から使えるようになったわけではありません。
あくまでdispatch_once
が使えなくなったということです。
final
で継承を禁止し、さらにイニシャライザーをprivate
にすることで自由なインスタンス生成を禁止し、static let
として公開したプロパティを通じてのみ唯一のインスタンスにアクセスできるようにします。
final class ClassA { static let sharedInstance = ClassA() private init() { } }
おわりに
今回はSwift3におけるGCDの基本的な使い方について書いてみました。GCDは非常に奥が深い世界なので一朝一夕に全てを理解することは難しいかもしれませんが、基本的なことから学んでいきたいと思います。