こんにちは、こばやしよしのり @yoshiii514 です。
Swiftでのアプリ開発では、必ず非同期処理の理解が必要になります。プログラミング初心者向けに非同期処理、ディスパッチキューを解説しました。
【目 次】
前提知識
本記事では、キュー、スレッド、同期、非同期といった用語がたくさんでてきます。これらの用語は「基本情報技術者試験」で学習できます。もし、用語が正確にわからない方は、次の本で学習することをおすすめします😊
Xcode、Swiftの基礎学習をしていることも前提になります。初学者の方は、次の記事を参考に基礎学習を済ませてください。
初心者向け iPhone(iOS)アプリ開発おすすめ本と勉強方法!
GDP(Grand Central Dispatch)の特徴
GDPは、非同期処理を用意にするためのC言語ベースの技術。
- キューを使って非同期処理
- 直接にスレッドは管理しない
- タスクをキューに追加するだけでいい
- あらかじめ準備された空きスレッドを使う(スレッドプール)
ディスパッチキュー:dispatch queue
GDPではキューのことをディスパッチキューを呼ぶ。次の2つの種類がある。
キューは実行するまでのタスク保管場所で、キューから取り出し実行したタスクの終了判定をするのか、しないのかで種類がわかれる。
ディスパッチキュー:dispatch queue の種類
ディスパッチキューにはタスクの実行方式や、作り方から複数の種類にわかれる。
■タスクの実行方式による種類
①直列ディスパッチキュー(serial dispatch queue)
現在実行中の処理を終了をまってから、次の処理を実行する。処理の終了と同期するので同期型。
②並列ディスパッチキュー(parallel dispatch queue)
現在実行中の処理の終了を待たずに、次の処理を並列して実行する。処理の終了とは同期しないので非同期型。
■キューの作り方による種類
既存のキューを使うか、新規でキューを作るかの作り方
|ーー>既存のディスパッチキュー
| |ーー>メインキュー(main queue)・・・直列(同期型)
| |ーー>グローバルキュー(global queue)・・・並列(非同期型)
|
|ーー>新規のディスパッチキュー
|ーー>DispatchQueue型のイニシャライザ
メインキュー(main queue)
- メインスレッドでタスクを実行する直列ディスパッチキュー
- ユーザーインターフェースの更新は常にメインキューで実行
メインキューを取得するコード
DispatchQueue型のmainクラスプロパティを使う。
import Dispatch
// メインキューを取得
let mainQueue = DispatchQueue.main
グローバルキュー(global queue)
- 並列ディスパッチキュー
- 実行優先度を指定して取得
実行優先度のことをQoS(Quality of Service)といい、優先度が高い順に次の5種類がある。
優先度1:userInteractive(ユーザーインタラクティブ)
- もっとも優先順位が高い
- ユーザーの入力に対してインタラクティブに実行
- 即時に実行されなければフリーズしているように見える処理に使う
優先度2:userInitiated(ユーザーイニシェイド)
- ユーザーの入力を受けて実行される処理に使う
優先度3:default(デフォルト)
- userInitiatedとutilityの中間の実行優先度
- QoSが何も指定されていない場合に利用
- 明示的には指定しない
優先度4:utility
- プログレスバー付きのダウンロードなどに利用される
- 視覚的な情報の更新をするが、即時の結果を要求しない処理に使う
優先度5:background
- もっとも優先順位が低い
- バックアップなどの見えないところで使われる
- 時間がかかっても問題ない処理に使う
グローバルキューを取得するコード
DispatchQueue型のglobal(qos:)クラスメソッドを使う。引数にDispatchQoS.QoSClass型の値を指定。
import Dispatch
// グローバルキューを取得
let queue = DispatchQueue.global(qos: .userInitiated)
QoSで指定した優先で処理の実行順序が決定される。専用のディスパッチキューを必要とする非同期処理でなければ、通常はグローバルキューを使う。
新規のディスパッチキュー生成
import Dispatch
// com.ticklecode.my_app.upload_queueという名前の並列ディスパッチキュー生成
let queue = DispatchQueue(label: "com.ticklecode.my_app.upload_queue",
qos: .default,
attributes: [.concurrent])
- 引数labelはディスパッチキューの名前。Xcode上のデバック時に参照できるので、他のライブラリで使用されているキューと区別できるような名前にする。逆順DNS形式がよい
- 引数qosは、生成するディスパッチキューのQoS
- 引数attributesは、付加オプション。.concurrentを追加すると並列ディスパッチキューが生成、追加しなければ直列ディスパッチキューを生成
ディスパッチキューへのタスク追加
次の例では、並列ディスパッチキューに対して、async(excute:)メソッドで非同期処理。
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
print(Thread.isMainThread) // false
print("非同期の処理")
}
- DispatchQueue型のasync(excute:)メソッドを使う
- ディスパッチキューではタスクはクロージャになり、async(excute:)メソッドの引数で指定
- 直列・並列もタスク追加方法は同じ
「Thread.isMainThread」で、現在の処理がメインスレッドで実行されているのかを確認することができる。この例ではfalseになる。
GDPを利用するケース
シンプルな非同期処理
GCDではタスクをクロージャとして表現できるので、単純な非同期処理の実装に向いている。
メインスレッドから時間のかかる処理を別スレッドにして、その別スレッドが終了したらメインスレッドを実行する例を考える。DispatchQueue.globalとDispatchQueue.mainを組み合わせると実現できる。
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
print("①非同期の処理")
print(Thread.isMainThread) // false
print("②3秒スリープ")
sleep(3)
let queue = DispatchQueue.main
queue.async {
print("③メインスレッドの処理")
print(Thread.isMainThread) // true
}
}
print("④10秒スリープ")
sleep(10)
print("⑤コードの最後")
// 実行結果
④10秒スリープ
①非同期の処理
false
②3秒スリープ
⑤コードの最後
③メインスレッドの処理
true
上記コードのように、DispatchQueue.globalは非同期なので、他の実行中のタスクとは無関係に単独で処理される。DispatchQueue.mainは実行中の処理が終了したら処理されているのがわかる。
このぐらいのシンプルな処理であれば、新規にディスパッチキューを作らなくても、既存のディスパッチキューのみ実現が可能。
GDPでは難しいケース
- タスク同士の依存関係の定義
- 条件に応じたタスクのキャンセル
GDPは、上記のような複雑な非同期には向かない。複雑な非同期処理には、OperationクラスとOperationQueueクラスを利用する。
参考図書
今回の非同期処理では、次の本を参考にしました。とてもわかりやすい良書ですので、おすすめです。
この記事がお役に立てましたら、ぜひTwitterで感想をお聞かせください😊
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。
グループのメンバーとして参加する
本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!
まずは、現役エンジニアに相談できる無料相談をご利用ください。