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

この記事からわかること

  • SwiftDispatchQueueとは?
  • DispatchQueueを使った非同期処理
  • GCD(Grand Central Dispatch)とは?
  • mainQueueglobalQueue違い
  • 一定時間後遅延処理実行させる方法の意味
  • Private Queue自作方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

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メソッドの引数qosDispatchQoS構造体のプロパティで指定します。

 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メソッドを使用すれば一定時間後に任意の処理を実行させることが可能(遅延処理)です。

遅延させたい時間は引数deadlineDispatchTime型の値を指定します。

func asyncAfter(
    deadline: DispatchTime,
    execute: DispatchWorkItem
)

DispatchTime.now()現在の時間を取得できるので遅延させたい秒数を+すればその秒数分遅延させて処理を実行することができます。

DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + 1) {
  // 非同期で1秒後に実行される  
  print("Hello World!") 
  print("こんにちは世界!")
}
print("End")

ミリ秒、ナノ秒、マイクロ秒で遅延させる

公式リファレンス:DispatchTimeInterval

遅延させる時間をミリ秒、ナノ秒、マイクロ秒で指定したい場合は列挙型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を使用します。詳細な実装方法は以下の記事を参考にしてください。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

私がSwift UI学習に使用した参考書

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index