Swiftでの処理待ちの5つの方法

Swiftのイラストで、「処理待ち」という文字が表示されているSwift
この記事は約17分で読めます。

 

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

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

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

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

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

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

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

はじめに

皆さんはSwiftでのプログラミング中に、ある処理が完了するまで待つ必要がある場面に遭遇したことはありますか?

特に、外部のサービスやデータベースにアクセスする際、その処理が完了するのを待って次の処理に進める必要があります。

しかし、正しい方法で待たないと、アプリがフリーズしたり、思わぬバグの原因となることも。

今回は、Swiftでの処理を一時停止し、特定の処理が終わるまで待つ方法を5つ解説します。

この記事を読めば、Swiftでの処理待ちの方法を5つ習得することができます。

●Swiftと処理待ちの必要性

Swiftは、Appleが提供するiOSやmacOSのアプリケーション開発言語です。

多くのアプリケーション開発者が使用しており、日々さまざまなアプリが生まれています。

その中で、外部リソースにアクセスする場面や、特定の処理が完了するのを待つ必要がある場面は頻発します。

○Swiftとは?

Swiftは、CやObjective-Cとは異なる、新しいプログラミング言語です。

2014年にAppleによって公開され、それ以降iOSやmacOSのアプリケーション開発に広く採用されています。

その特徴として、読みやすく、安全性が高く、高速に動作する点が挙げられます。

○非同期処理とは?

非同期処理とは、メインの処理フローを停止させずに、別の処理を並行して実行する手法のことを指します。

例えば、画像のダウンロードやデータベースへのアクセスなど、時間がかかる処理をバックグラウンドで実行し、メインの処理はその完了を待たずに先に進むことができます。

○同期処理と非同期処理の違い

同期処理は、一つ一つの処理が順番に実行され、一つの処理が終了しない限り次の処理に進むことができません。

これに対して、非同期処理は、複数の処理を同時に進行させることができるので、時間がかかる処理があってもその完了を待たずに次の処理に進むことができます。

しかし、非同期処理を行う際は、処理が終了した時の挙動や、処理の順番などに注意が必要です。

Swiftでは、非同期処理を簡単に実装するための多くの機能やライブラリが提供されています。しかし、その方法は一つではありません。

今回は、その中から特に代表的な5つの方法を取り上げ、詳しく解説していきます。

●Swiftでの処理待ちの5つの方法

Swiftでのプログラミングでは、ある処理が完了するのを待つ、いわゆる「処理待ち」の必要性が頻発します。

それは、APIからのデータの取得や、ユーザーのアクションの反映など、さまざまな場面で遭遇する課題です。

今回は、Swiftでの処理待ちを実現する5つの方法を詳細にご紹介します。

○サンプルコード1:Grand Central Dispatch (GCD) を利用した基本的な方法

Grand Central Dispatch、通称GCDは、Appleが提供する並列処理のためのAPIです。

非常に高速で、簡単に非同期処理を実装することができます。

特に、DispatchQueueを使って、指定したタスクを非同期に実行することが可能です。

// Swiftのサンプルコード
import Foundation

// 非同期で実行するタスク
let task = {
    sleep(2) // 2秒待つ
    print("タスク完了")
}

// バックグラウンドでタスクを実行
DispatchQueue.global().async {
    task()
    // メインスレッドで結果を表示
    DispatchQueue.main.async {
        print("非同期処理が終了しました")
    }
}

print("非同期処理を開始")

このコードでは、DispatchQueue.global().asyncを使ってバックグラウンドでtaskを非同期に実行しています。

taskが完了した後、DispatchQueue.main.asyncを使用して、メインスレッドで結果を表示しています。

このコードを実行すると、”非同期処理を開始”と表示された後、2秒待ってから”タスク完了”、”非同期処理が終了しました”という順で表示されます。

○サンプルコード2:ディスパッチキューを使用した方法

ディスパッチキューは、指定したタスクを指定した順序で実行するためのキューです。

DispatchQueueを利用することで、非同期にタスクを実行したり、タスクを一時的に待機させることができます。

// Swiftのサンプルコード
import Foundation

let myQueue = DispatchQueue(label: "com.example.myqueue") // カスタムキューの作成

myQueue.async {
    print("非同期に実行されるタスク1")
    sleep(2) // 2秒待つ
    print("タスク1完了")
}

myQueue.async {
    print("非同期に実行されるタスク2")
    sleep(1) // 1秒待つ
    print("タスク2完了")
}

print("非同期処理を開始")

このコードでは、まずカスタムのディスパッチキューmyQueueを作成しています。

その後、このキューに2つのタスクを非同期に追加して実行しています。ディスパッチキューに追加されたタスクは、追加された順に実行されます。

このコードを実行すると、”非同期処理を開始”と表示された後、”非同期に実行されるタスク1″、2秒待ってから”タスク1完了”、”非同期に実行されるタスク2″、1秒待ってから”タスク2完了”という順で表示されます。

○サンプルコード3:ディスパッチセマフォを使用した方法

ディスパッチセマフォは、特定の数のリソースにアクセスするための制限を提供します。

主に、複数のタスクが同時に同じリソースにアクセスすることを防ぐために使用されます。

セマフォは、指定した数だけの「許可」を持っており、この「許可」があればリソースにアクセスできます。

具体的には、DispatchSemaphoreクラスを使ってセマフォを作成します。

そして、wait()メソッドで「許可」を待ち、signal()メソッドで「許可」を返します。

ここでは、ディスパッチセマフォを使用したサンプルコードを紹介します。

import Foundation

// セマフォを作成。初期の「許可」は1とする。
let semaphore = DispatchSemaphore(value: 1)

DispatchQueue.global().async {
    semaphore.wait() // 「許可」を待つ
    print("非同期タスク1を実行中")
    sleep(2) // 2秒待機
    print("非同期タスク1完了")
    semaphore.signal() // 「許可」を返す
}

DispatchQueue.global().async {
    semaphore.wait() // 「許可」を待つ
    print("非同期タスク2を実行中")
    sleep(1) // 1秒待機
    print("非同期タスク2完了")
    semaphore.signal() // 「許可」を返す
}

このコードでは、セマフォの初期「許可」を1としています。

これにより、2つの非同期タスクが同時に実行されないように制限されます。

結果として、非同期タスク1が完了してから非同期タスク2が開始されます。

このコードを実行すると、まず”非同期タスク1を実行中”と表示され、2秒後に”非同期タスク1完了”と表示されます。

その後、”非同期タスク2を実行中”と表示され、1秒後に”非同期タスク2完了”と表示されます。

○サンプルコード4:クロージャを使用した方法

クロージャは、関数と同じ機能を持つが、独立した名前を持たないコードブロックです。

Swiftでは、クロージャを利用して、処理の完了後に何をするかを指定することができます。

ここでは、クロージャを利用して非同期処理の完了後の動作を指定したサンプルコードを紹介します。

import Foundation

func performTask(completion: @escaping () -> Void) {
    DispatchQueue.global().async {
        print("非同期でタスクを実行中")
        sleep(2) // 2秒待機
        completion()
    }
}

performTask {
    print("非同期タスクが完了しました")
}

このコードでは、performTask関数が非同期でタスクを実行し、その完了後にクロージャ内のコードが実行されます。

このコードを実行すると、”非同期でタスクを実行中”と表示され、2秒後に”非同期タスクが完了しました”と表示されます。

○サンプルコード5:RunLoopを利用した方法

RunLoopは、アプリケーションのメインスレッドでのイベントの処理を管理するためのものです。

Swiftでは、RunLoopを使って、特定の条件が満たされるまで現在のスレッドの実行を一時停止することができます。

ここでは、RunLoopを利用して特定の条件が満たされるまで処理を待機させるサンプルコードを紹介します。

import Foundation

var taskIsDone = false

DispatchQueue.global().async {
    print("非同期でタスクを実行中")
    sleep(2) // 2秒待機
    taskIsDone = true
}

while !taskIsDone {
    RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.1))
}

print("非同期タスクが完了しました")

このコードでは、非同期でタスクを実行している間、RunLoop.current.runを使って現在のスレッドを0.1秒間隔で一時停止させています。

非同期タスクが完了したら、taskIsDonetrueになり、ループが終了します。

このコードを実行すると、”非同期でタスクを実行中”と表示され、2秒後に”非同期タスクが完了しました”と表示されます。

●処理待ちの応用例

Swiftの非同期処理は多様な場面で利用されています。

特にAPIの通信やデータベースの操作、UIのアップデートなど、一定の待ち時間や順序を必要とするタスクにおいては、適切な待ち処理を実装することが不可欠となります。

ここでは、実際のアプリ開発の場面でよく使用される応用例を3つ取り上げ、詳しく解説します。

○サンプルコード6:APIのデータ取得を待つ例

Web APIとの通信は、ネットワークのレスポンスにラグがあるため、非同期処理として実装することが一般的です。

ここでは、非同期でAPIを呼び出し、データを取得する際のサンプルコードを紹介します。

import Foundation

let url = URL(string: "https://api.example.com/data")!

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard let data = data, error == nil else {
        print("エラー: \(error?.localizedDescription ?? "未知のエラー")")
        return
    }

    // データを解析
    let jsonData = try? JSONSerialization.jsonObject(with: data, options: [])
    print(jsonData)
}

task.resume()

このコードでは、URLSessionを使ってAPIを非同期に呼び出しています。

取得したデータはJSON形式として解析され、結果がコンソールに出力されます。

このコードを実行すると、APIから取得したデータが表示されることが期待されます。

○サンプルコード7:データベースの読み書きを待つ例

アプリケーションの動作中にデータベースへの読み書きを行う際も、非同期処理を適切に用いることで、ユーザー体験の向上やアプリのレスポンスを保持することができます。

import SQLite3

var db: OpaquePointer?
let databasePath = "path_to_database.db"

if sqlite3_open(databasePath, &db) != SQLITE_OK {
    print("データベースのオープンに失敗")
    return
}

let query = "SELECT name FROM users WHERE id = 1"
var statement: OpaquePointer?

if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK {
    while sqlite3_step(statement) == SQLITE_ROW {
        if let cName = sqlite3_column_text(statement, 0) {
            let name = String(cString: cName)
            print(name)
        }
    }
}

sqlite3_finalize(statement)
sqlite3_close(db)

このコードでは、SQLite3を使ってデータベースからデータを非同期に読み取っています。

特定のユーザーIDを条件として、名前を取得しています。

このコードを実行すると、指定したIDのユーザーの名前がコンソールに表示されることが期待されます。

○サンプルコード8:ユーザーのインプットを待つ例

ユーザーからのインプットを受け付けるアクションは、その応答を待って処理を進める必要があります。

例えば、UIAlertControllerを使用してユーザーにテキスト入力を求める場合のサンプルは次の通りです。

import UIKit

let alertController = UIAlertController(title: "名前入力", message: "あなたの名前を入力してください", preferredStyle: .alert)
alertController.addTextField { (textField) in
    textField.placeholder = "名前"
}

let okAction = UIAlertAction(title: "OK", style: .default) { (action) in
    if let name = alertController.textFields?.first?.text {
        print("こんにちは、\(name)さん!")
    }
}

alertController.addAction(okAction)
present(alertController, animated: true, completion: nil)

このコードでは、UIAlertControllerを使ってダイアログを表示し、ユーザーに名前の入力を求めています。

OKボタンをクリックすると、入力された名前を使って挨拶メッセージがコンソールに表示されます。

このコードを実行すると、ユーザーの入力した名前を使った挨拶が表示されることが期待されます。

●注意点と対処法

Swiftでの非同期処理は多くの利点がある一方で、誤った実装や理解不足からくる問題点もあります。

ここでは、非同期処理の際に知っておくべき注意点と、それらの問題を解決するための対処法を詳しく解説します。

○非同期処理の落とし穴

非同期処理は処理の順番が保証されないため、複数の非同期タスクが絡む場合、期待した動作と異なる結果を引き起こす可能性があります。

たとえば、データベースへの書き込みとその後の読み取りを非同期で実行する場合、読み取りが書き込みよりも先に実行されると、古いデータが取得される可能性があります。

○メインスレッドとバックグラウンドスレッド

非同期処理を行う際、メインスレッドとバックグラウンドスレッドの違いを理解しておくことは重要です。

メインスレッドは主にUIの更新などのタスクに使用され、バックグラウンドスレッドはデータの処理やネットワークの通信など、時間がかかるタスクに使用されます。

メインスレッドで時間のかかる処理を行うと、アプリケーションの応答性が低下し、ユーザー体験が悪化します。

そのため、時間がかかる処理はバックグラウンドスレッドで行い、終了後にメインスレッドでUIを更新するという流れを取ることが多いです。

○デッドロックを避ける方法

デッドロックとは、2つ以上のタスクがお互いに必要なリソースを持っていて、それが解放されるのを永遠に待ち続ける状態を指します。

Swiftでの非同期処理においても、デッドロックは発生する可能性があります。

例えば、メインスレッドでディスパッチキューを同期的に実行しようとすると、デッドロックのリスクが高まります。

DispatchQueue.main.sync {
    // ここでの処理はメインスレッドで行われます
}

このコードをメインスレッドで実行すると、デッドロックが発生します。

なぜなら、メインスレッドはすでに処理中であり、同じスレッド上でさらに同期的な処理を待ってしまうためです。

このような状況を避けるためには、同期処理を行う際は現在のスレッドと実行するキューが同じでないかを常に確認することが重要です。

デッドロックを避けるためのもう一つの方法として、必要なリソースのアクセス順序を明確にすることも考えられます。

同時に複数のリソースにアクセスする必要がある場合、常に同じ順序でアクセスすることでデッドロックのリスクを低減することができます。

●カスタマイズ方法

Swiftでの非同期処理は高いパフォーマンスとユーザー体験を提供するために欠かせないものですが、実際の使用場面や要件に応じて処理をカスタマイズすることが求められることも多いです。

ここでは、非同期処理をより効果的に使うためのカスタマイズ方法をいくつか紹介します。

○処理の優先順位を変更する方法

Swiftの非同期処理では、処理の優先順位を変更することが可能です。

特にGrand Central Dispatch(GCD)を使用した非同期処理では、ディスパッチキューの優先順位を指定して、タスクの実行順序を制御することができます。

例として、ディスパッチキューの優先順位を変更するサンプルコードを紹介します。

// 高優先度のキュー
let highPriorityQueue = DispatchQueue.global(qos: .userInteractive)
highPriorityQueue.async {
    // 高優先度で実行したいタスク
}

// 低優先度のキュー
let lowPriorityQueue = DispatchQueue.global(qos: .background)
lowPriorityQueue.async {
    // 低優先度で実行したいタスク
}

このコードでは、userInteractiveの優先順位で高優先度のタスクを実行し、backgroundの優先順位で低優先度のタスクを実行しています。

これにより、リソースの競合が発生した際に、高優先度のタスクが先に実行されることを保証できます。

○特定の条件で処理をスキップする方法

非同期処理の中には、特定の条件下で実行をスキップしたい場合があります。

例えば、APIからデータを取得する非同期タスクがあり、ユーザーが特定の画面にいない場合にはそのタスクを実行しないようにしたいという場面が考えられます。

ここでは、特定の条件下で非同期処理をスキップするサンプルコードを紹介します。

let isUserOnScreen = true // ユーザーが画面にいるかどうかのフラグ

DispatchQueue.global().async {
    if isUserOnScreen {
        // ユーザーが画面にいる場合のみ実行するタスク
    } else {
        // スキップしたい処理やログの出力など
    }
}

このコードでは、isUserOnScreenのフラグを使用して、ユーザーが画面に存在する場合のみ非同期処理を実行しています。

このように、特定の条件下で非同期処理を制御することで、不必要な処理を削減し、リソースを効率的に使用することができます。

まとめ

Swiftにおける非同期処理は、アプリケーションのレスポンス性やパフォーマンスを向上させる上で不可欠な要素となっています。

本記事では、Swiftでの非同期処理の基本から応用、カスタマイズ方法までを詳細に解説しました。

各非同期処理の方法やその特性、そしてカスタマイズ方法を理解することで、様々なシチュエーションに適切に対応することが可能となります。

Swiftの非同期処理には多くの可能性が秘められており、今後もその活用範囲は広がっていくでしょう。

日々の開発において、本記事が皆様の参考となり、Swiftの非同期処理をより深く、より実践的に活用する手助けとなることを願っています。