SwiftでのDispatchQueueの使い方!初心者向けの10選で解説

SwiftのDispatchQueueを使ったコーディングのサンプルイメージSwift
この記事は約19分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swift言語はAppleが開発したプログラミング言語で、iOS、macOS、watchOS、tvOSといったAppleのプラットフォームを対象としたアプリ開発に広く使われています。

初心者から経験者まで、多くの開発者がSwiftを使用して、効率的で安全なコードを書くための特徴や機能を利用しています。

その中で、特に重要な概念の1つが「非同期処理」です。

非同期処理を効果的に行うためのツールとして、DispatchQueueが提供されています。

●SwiftとDispatchQueueの基本

Swiftは、どのような特長や背景を持つ言語なのでしょうか。

また、DispatchQueueという機能は、具体的にどのような場面でどのように活用されるのでしょうか。

ここでは、これらの疑問に答えながら、SwiftとDispatchQueueの基本について詳しく説明していきます。

○Swiftとは

Swiftは、Appleが2014年に公開した新しいプログラミング言語です。

Objective-Cの後継として開発され、より直感的で安全性を重視した言語設計がなされています。

型安全性やオプショナル、そしてプロトコル指向プログラミングなどの特長を持つSwiftは、開発者から高い評価を受けています。

○DispatchQueueとは

DispatchQueueは、SwiftやObjective-Cにおいて非同期処理や並列処理を行うための機能を提供するクラスです。

タスクをキューに追加して、指定したスレッドで実行することができます。

主に、メインスレッドでのUI更新や、バックグラウンドでの重い処理など、特定のタスクを特定のスレッドで行いたい場合に使用されます。

●DispatchQueueの使い方

DispatchQueueはSwiftで非同期処理や並列処理を行うための強力なツールです。

その基本的な使い方を理解し、それを実際のコードでどのように利用するかを学ぶことで、アプリのパフォーマンスや応答性を向上させることができます。

○サンプルコード1:メインスレッドでの処理

メインスレッドでは、主にUIの更新などの作業が行われます。

非同期処理を使用すると、重いタスクがメインスレッドで実行されるのを防ぎながら、UIの更新を行うことができます。

import UIKit

let mainQueue = DispatchQueue.main

mainQueue.async {
    // UIの更新処理をこちらで行う
    print("UIを更新するコードを実行")
}

このコードでは、DispatchQueue.mainを使ってメインスレッドにアクセスしています。

asyncメソッドを使用することで、非同期的にUIの更新処理を行っています。

この例では、print文を使用してUIの更新処理をシミュレートしています。

このコードを実行すると、コンソールに”UIを更新するコードを実行”と表示されます。

メインスレッドでのUIの更新は、アプリのユーザビリティやレスポンスを向上させるための基本的なテクニックです。

○サンプルコード2:バックグラウンドでの処理

重たいタスクや時間のかかる処理は、バックグラウンドスレッドで行うことでメインスレッドの負担を軽減し、アプリの応答性を保つことができます。

import UIKit

let backgroundQueue = DispatchQueue.global(qos: .background)

backgroundQueue.async {
    // 重いタスクをこちらで行う
    for i in 1...10000 {
        print("\(i)回目のループ処理")
    }
    DispatchQueue.main.async {
        // 処理完了後のUI更新はメインスレッドで
        print("バックグラウンド処理完了")
    }
}

このコードでは、DispatchQueue.global(qos: .background)を使用してバックグラウンドスレッドにアクセスしています。

その後、非同期的にループ処理を行っています。最後に、バックグラウンドでの処理が完了した後のUIの更新をメインスレッドで行っています。

このコードを実行すると、コンソールに”1回目のループ処理”から”10000回目のループ処理”までのメッセージが表示され、その後”バックグラウンド処理完了”と表示されます。

バックグラウンドスレッドでの処理は、アプリのパフォーマンスや応答性を向上させるための重要な手段です。

○サンプルコード3:非同期処理の基本

Swiftでの非同期処理の基本は、DispatchQueueを使用して実装することができます。

非同期処理とは、メインスレッドとは異なるスレッドでタスクを実行することを意味します。

これにより、重い処理をバックグラウンドで行いつつ、ユーザーインターフェースは滑らかに動作することが可能となります。

具体的に、Swiftでの非同期処理の基本を実装するサンプルコードを紹介します。

import UIKit

// UIViewController内部での使用例
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 非同期処理の実行
        DispatchQueue.global().async {
            // こちらの処理はバックグラウンドで実行されます
            let data = self.fetchDataFromServer()

            DispatchQueue.main.async {
                // こちらの処理はメインスレッドで実行されます
                self.updateUI(with: data)
            }
        }
    }

    func fetchDataFromServer() -> String {
        // データの取得処理(ダミー)
        sleep(2) // 2秒間の待機を表す
        return "Fetched Data"
    }

    func updateUI(with data: String) {
        // UIの更新処理
        print(data)
    }
}

このコードでは、DispatchQueue.global().asyncを使って非同期にデータの取得を行っています。

そして、データの取得が完了した後、DispatchQueue.main.asyncを使ってメインスレッド上でUIの更新を行っています。

この例では、fetchDataFromServerメソッドで2秒間の待機を行い、その後”Fetched Data”という文字列を返して、それをメインスレッドで表示しています。

このコードを実行すると、アプリは2秒間の待機後に”Fetched Data”という文字列をコンソールに表示します。

このように、DispatchQueueを使用することで、非同期処理を簡単に実装することができます。

○サンプルコード4:同期処理の基本

一方、非同期処理の対照的に存在する同期処理とは、タスクを順序通りに直列で実行する方法です。

同期処理を行う場合、前のタスクが完了するまで次のタスクは待機する必要があります。

import UIKit

// UIViewController内部での使用例
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 同期処理の実行
        DispatchQueue.global().sync {
            // この処理はバックグラウンドで同期的に実行されます
            self.processData()
        }

        print("This will be printed after processData method is completed.")
    }

    func processData() {
        // 何らかの処理(ダミー)
        sleep(3) // 3秒間の待機を表す
        print("Data processed.")
    }
}

このコードでは、DispatchQueue.global().syncを使って、processDataメソッドを同期的に実行しています。

その後、同期処理が完了したら、メッセージをコンソールに表示しています。

このコードを実行すると、まず”Data processed.”という文字列が3秒後に表示され、その後に”This will be printed after processData method is completed.”という文字列が表示されます。

これは、processDataメソッドが完了するまで、次のprint文の実行を待機しているためです。

このように、同期処理を行う場合は、前のタスクが完了するまで次のタスクの実行を待つ必要があります。

●DispatchQueueの応用例

DispatchQueueは非常に強力で、さまざまなシチュエーションで役立ちます。

ここでは、応用的な使い方のいくつかを、具体的なサンプルコードとともに紹介します。

○サンプルコード5:非同期処理での連続タスク

このコードでは、非同期処理を用いて連続してタスクを実行する方法を表しています。

この例では、複数の非同期タスクを順番に実行しています。

import Foundation

// 非同期処理のQueueを作成
let queue = DispatchQueue(label: "com.example.myQueue")

queue.async {
    print("タスク1開始")
    sleep(2)  // 2秒待つ
    print("タスク1終了")
}

queue.async {
    print("タスク2開始")
    sleep(1)  // 1秒待つ
    print("タスク2終了")
}

queue.async {
    print("タスク3開始")
    sleep(3)  // 3秒待つ
    print("タスク3終了")
}

このコードを実行すると、”タスク1開始”から”タスク3終了”までの順番で出力されることを確認できます。

しかし、実際にはタスク間の待機時間が存在するため、完全な終了までには合計6秒の時間がかかります。

○サンプルコード6:DispatchGroupを使ったタスク管理

DispatchGroupは、複数のタスクを一つのグループとして管理するための便利なツールです。

このコードでは、DispatchGroupを使って非同期処理の完了を監視し、全てのタスクが終了した時点で特定の処理を実行する方法を示しています。

import Foundation

let group = DispatchGroup()
let queue = DispatchQueue(label: "com.example.groupQueue")

queue.async(group: group) {
    print("タスクA開始")
    sleep(2)
    print("タスクA終了")
}

queue.async(group: group) {
    print("タスクB開始")
    sleep(1)
    print("タスクB終了")
}

group.notify(queue: .main) {
    print("全てのタスク完了")
}

上記のコードでは、タスクAとタスクBが非同期で実行されます。

両方のタスクが完了した時点で”全てのタスク完了”というメッセージが表示されることを確認できます。

この例では、タスクA、タスクBの実行後に”全てのタスク完了”が表示される流れになります。

○サンプルコード7:DispatchSemaphoreを使ったタスクの同期

DispatchSemaphoreはSwiftでの同期処理に使われるクラスの一つです。

このクラスを利用することで、複数のタスクが同時に実行されるのを制御することができます。

具体的には、あるタスクが完了するまで、次のタスクが開始されないようにするという動作をします。

このセマフォという概念は、信号を持っているオブジェクトとして考えることができます。

セマフォの信号が緑のときには、タスクを進行させることができます。

しかし、信号が赤のときには、タスクの進行を待たせることができます。

この制御を使って、タスク間の同期をとることができるのです。

ここではDispatchSemaphoreを使って、2つのタスクを順番に実行するサンプルコードを紹介します。

import Foundation

let semaphore = DispatchSemaphore(value: 1)

// タスク1
DispatchQueue.global().async {
    semaphore.wait() // 信号待ち

    print("タスク1開始")
    sleep(2) // 2秒待機(何らかの処理を模倣)
    print("タスク1完了")

    semaphore.signal() // 信号を緑にする
}

// タスク2
DispatchQueue.global().async {
    semaphore.wait() // 信号待ち

    print("タスク2開始")
    sleep(1) // 1秒待機(何らかの処理を模倣)
    print("タスク2完了")

    semaphore.signal() // 信号を緑にする
}

このコードでは、DispatchSemaphoreオブジェクトを作成し、その後2つのタスクを非同期に実行しています。

タスクが開始される前にはsemaphore.wait()を使用して、セマフォの信号を待っています。

タスクが完了したら、semaphore.signal()を使って、次のタスクが開始できるようにセマフォの信号を緑にしています。

この例では、タスク1が完了するまで、タスク2は開始されません。

このように、DispatchSemaphoreを使用することで、タスク間の実行順序を制御することができます。

○サンプルコード8:非同期処理でのエラーハンドリング

非同期処理中にエラーが発生した場合、エラーハンドリングが重要になります。

Swiftでは、非同期処理の中でエラーが発生した場合に、適切にキャッチし、処理を行うための方法が提供されています。

ここでは非同期処理中にエラーが発生する場合のサンプルコードを紹介します。

import Foundation

enum CustomError: Error {
    case unknownError
}

func asyncFunction(completion: @escaping (Result<String, CustomError>) -> Void) {
    DispatchQueue.global().async {
        // 何らかの処理を行い、エラーが発生した場合にはCustomErrorを返す
        completion(.failure(.unknownError))
    }
}

asyncFunction(completion: { result in
    switch result {
    case .success(let message):
        print(message)
    case .failure(let error):
        print("エラーが発生しました: \(error)")
    }
})

この例では、非同期関数asyncFunction内でエラーが発生した場合に、Result型を使用してエラー情報をキャッチしています。

このResult型は、成功時の結果とエラー時の情報のどちらかを持っている型です。

この型を使うことで、非同期処理の中でのエラーハンドリングを行うことができます。

このサンプルコードを実行すると、「エラーが発生しました: unknownError」と表示されます。

このように、非同期処理中のエラーハンドリングをSwiftではResult型を利用して実装することが推奨されています。

●注意点と対処法

SwiftでのDispatchQueueの利用においては、特に初心者が陥りがちな落とし穴がいくつか存在します。

これらの問題に直面した際の対処法を理解することは、より効率的で安全なプログラミングに不可欠です。

○メモリリークのリスクとその回避方法

DispatchQueueを使用する際に、最も注意しなければならないのがメモリリークです。

特に非同期処理を行う際には、クロージャ(無名関数)の中でself(自身のインスタンス)を参照することが多々あります。

この際、誤って強参照サイクルを形成してしまうと、メモリリークを引き起こす原因となります。

対処法として、強参照サイクルを避けるためには、クロージャのキャプチャリストで[weak self][unowned self]を使用します。

weakは自身への弱い参照を作成し、オプショナル型になります。

unownedは、自身が必ず存在するという前提での弱い参照を作成し、オプショナルではない形で参照します。

class MyClass {
    var property: String = "データ"

    func fetchData() {
        DispatchQueue.global().async { [weak self] in
            guard let strongSelf = self else {
                return
            }
            print(strongSelf.property)
        }
    }
}

このコードでは[weak self]を用いて、MyClassのインスタンス(self)への参照を弱くしています。

guard let strongSelf = selfselfがまだメモリ上に存在していることを確認しています。もし存在しなければ、クロージャ内の処理は実行されません。

○タスクのキャンセルとその注意点

DispatchQueueを使ってタスクを実行した後、特定の状況ではそれらのタスクをキャンセルする必要が出てくることがあります。

例えば、ユーザーが画面を離れた時や必要な情報がすでに得られた場合などです。

しかし、Swiftの標準ライブラリにおいて、DispatchQueueのタスクを直接キャンセルする機能は提供されていません。

対処法として、タスクをキャンセルする際の一般的なアプローチは、タスクの実行がまだ必要かどうかを定期的に確認することです。

これは、タスクの各段階でキャンセルフラグを確認することによって達成できます。

class TaskManager {
    var isCancelled = false

    func performLongRunningTask() {
        DispatchQueue.global().async {
            for i in 1...10 {
                if self.isCancelled {
                    print("タスクがキャンセルされました")
                    return
                }
                // 長い処理のシミュレーション
                sleep(1)
                print("タスク \(i) を完了")
            }
        }
    }

    func cancelTask() {
        isCancelled = true
    }
}

let manager = TaskManager()
manager.performLongRunningTask()

// ある条件下でタスクをキャンセル
manager.cancelTask()

このコードでは、TaskManagerクラスにisCancelledプロパティを持たせ、タスクがキャンセルされたかどうかの状態を保持しています。

タスク実行中にisCancelledtrueになった場合、タスクは途中で中止されます。

●カスタマイズ方法

SwiftのDispatchQueueは、その動作や振る舞いを変更するための多くのカスタマイズ方法を持っています。

ここでは、DispatchQueueのカスタム属性設定や優先度のカスタマイズ方法について詳しく解説します。

○サンプルコード9:DispatchQueueのカスタム属性設定

Swiftでは、DispatchQueueを作成する際に、特定の属性を持ったキューを作成することができます。

これにより、特定の動作や振る舞いを持つキューを設計することが可能になります。

下記のコードは、カスタム属性を持ったDispatchQueueを作成する例です。

// カスタム属性を持つDispatchQueueを作成
let customQueue = DispatchQueue(label: "com.example.customQueue", attributes: .concurrent)

// カスタムキューでタスクを実行
customQueue.async {
    print("カスタムキューで実行中のタスク")
}

このコードではDispatchQueueattributesパラメータに.concurrentを指定して、並行処理をサポートするキューを作成しています。

この例では、並行処理をサポートするカスタムキューでタスクを非同期に実行しています。

このコードを実行すると、”カスタムキューで実行中のタスク”というメッセージが表示されることが確認できます。

○サンプルコード10:DispatchQueueの優先度のカスタマイズ

DispatchQueueのタスクは、その実行優先度をカスタマイズすることができます。

優先度を変更することで、タスクの実行順序や処理速度を調整することが可能です。

下記のコードは、キューの優先度をカスタマイズする例を表しています。

// 優先度を指定してDispatchQueueを作成
let highPriorityQueue = DispatchQueue(label: "com.example.highPriority", qos: .userInteractive)

// 優先度が高いキューでタスクを実行
highPriorityQueue.async {
    print("優先度が高いキューで実行中のタスク")
}

このコードではDispatchQueueqosパラメータに.userInteractiveを指定して、ユーザーインタラクティブなタスクに適した優先度の高いキューを作成しています。

この例では、高い優先度を持つキューでタスクを非同期に実行しています。

このコードを実行すると、”優先度が高いキューで実行中のタスク”というメッセージが表示されることが確認できます。

まとめ

Swift言語でのDispatchQueueの利用は、非同期処理やマルチスレッド処理を効果的に実行するための鍵となります。

初心者から経験者まで、DispatchQueueの基本的な使い方から応用、カスタマイズ方法までを網羅的に理解することで、アプリケーションのパフォーマンスやユーザーエクスペリエンスの向上に貢献できます。

本記事では、DispatchQueueの基本的な動作原理から、非同期処理、同期処理、さらには応用的な使い方や注意点まで詳しく解説しました。

サンプルコードを通じて具体的な実装方法も学べる内容となっております。

Swiftを使用したアプリ開発において、多くの場面で非同期処理やマルチスレッド処理が必要となります。

DispatchQueueの知識は、そのようなシチュエーションでの開発をスムーズに進めるための強力な武器となるでしょう。

DispatchQueueの応用やカスタマイズ方法を学んで、より高度な処理や特定の要件に合わせたキューの作成が可能となることを理解してください。

これからのSwift開発において、DispatchQueueの知識が皆様の大きな助けとなることを願っています。