Swiftのasync/awaitのたった10選の使い方

Swiftの非同期処理async/awaitのロゴとコードのスクリーンショットSwift
この記事は約18分で読めます。

 

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

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

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

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

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

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

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

はじめに

Swiftは、AppleのiOS、macOS、watchOS、tvOSなどのプラットフォーム向けのプログラミング言語として知られています。

近年Swiftには多くの新しい機能が追加されていますが、中でも注目されるのが非同期処理の新しい方法、async/awaitです。

この記事では、async/awaitを利用した非同期処理の基本から、注意点、カスタマイズ方法まで、幅広く解説します。

●Swiftのasync/awaitとは

Swiftのasync/awaitは、非同期処理をよりシンプルに、そして読みやすく記述するための新しい機能です。

これにより、従来のコールバック地獄やネストが深くなる問題を解消し、コードの可読性を向上させることができます。

○非同期処理の基本

非同期処理とは、処理が終了するのを待たずに次の処理を進めることができる手法のことを指します。

例えば、ネットワーク通信やデータベースのクエリなど、時間がかかる可能性のある処理をバックグラウンドで実行し、その結果を後で取得することが可能になります。

この非同期処理により、アプリケーションの応答性を維持しつつ、ユーザーの操作に応じて背後で複数の処理を行うことができるようになります。

□従来の非同期処理との違い

Swiftにおける従来の非同期処理では、DispatchQueueOperationQueueFuturePromiseを利用した方法などが存在していました。

しかし、これらの方法はコードのネストが深くなる、エラーハンドリングが煩雑になるなどの問題点が指摘されていました。

それに対し、async/awaitを利用することで、非同期処理を直列的に記述することができるようになり、コードの可読性が大幅に向上します。

また、エラーハンドリングもよりシンプルになり、煩雑なコードの量を減少させることができます。

●async/awaitの基本的な使い方

Swiftの非同期処理の新機能、async/awaitは、従来の非同期処理の方法とは異なるアプローチを提供しています。

async/awaitを使うことで、非同期のコードを読みやすく、また書きやすくすることができます。

特に、多数の非同期タスクが連鎖するような場面では、この新しい構文が非常に有効です。

○サンプルコード1:基本的なasync/awaitの使用

Swiftのasync/awaitを使用するには、まず非同期関数を定義する必要があります。

非同期関数は、関数の定義の前にasyncキーワードを付けることで宣言できます。

// 非同期関数の定義例
func fetchData() async -> String {
    // 非同期処理を模倣するためにスリープを入れます。
    Thread.sleep(forTimeInterval: 1.0)
    return "Fetched Data"
}

このコードでは、fetchDataという非同期関数を定義しています。

この例では、1秒間スリープすることで非同期処理を模倣しています。

実際のアプリケーションでは、ネットワークリクエストやデータベースの操作などの非同期処理を行う場面でこの構文を使用します。

この非同期関数を呼び出すときは、awaitキーワードを使用します。

// 非同期関数の呼び出し例
func displayData() async {
    let data = await fetchData()
    print(data)
}

上記のコードでは、displayData関数内でfetchData関数をawaitを用いて呼び出しています。

これにより、非同期処理が完了するまで待機し、その結果を変数dataに格納しています。

その後、取得したデータをコンソールに表示しています。

このコードを実行すると、1秒後に”Fetched Data”という文字列がコンソールに表示されることが期待されます。

○サンプルコード2:非同期関数の呼び出し

非同期関数を連鎖的に呼び出す場面も多いです。

例えば、一つの非同期処理が完了した後に、その結果を元に別の非同期処理を行う場合などです。

async/awaitを使用することで、このような連鎖的な非同期処理も直感的に記述することができます。

func fetchDetailedData(baseData: String) async -> String {
    // ここでも非同期処理を模倣するためにスリープを入れます。
    Thread.sleep(forTimeInterval: 1.0)
    return baseData + ": Detailed Data"
}

func displayDetailedData() async {
    let data = await fetchData()
    let detailedData = await fetchDetailedData(baseData: data)
    print(detailedData)
}

このコードでは、fetchData関数で取得したデータを元に、更に詳細なデータを取得するfetchDetailedData関数を定義しています。

displayDetailedData関数内で、これらの非同期関数を連鎖的に呼び出しています。

このコードを実行すると、2秒後に”Fetched Data: Detailed Data”という文字列がコンソールに表示されることが期待されます。

●async/awaitを用いた応用例

Swiftの非同期処理であるasync/awaitは、簡潔で読みやすいコードを書くことができるのが特徴です。

しかし、基本的な使い方を超えて、さらに応用的な使い方を知ることで、より高度な非同期処理を実現することができます。

○サンプルコード3:非同期タスクの並行実行

このコードでは、async/awaitを使って複数の非同期タスクを並行して実行する方法を表しています。

この例では、2つの非同期タスクを作成して、それぞれの完了を待たずに並行して実行しています。

import Swift

// 非同期関数の定義
func fetchData1() async -> String {
    await Task.sleep(2 * 1_000_000_000) // 2秒待つ
    return "Data1"
}

func fetchData2() async -> String {
    await Task.sleep(1 * 1_000_000_000) // 1秒待つ
    return "Data2"
}

// 並行実行の例
async {
    let task1 = Task { await fetchData1() }
    let task2 = Task { await fetchData2() }

    let data1 = await task1.value
    let data2 = await task2.value
    print(data1, data2)
}

上記のコードでは、fetchData1fetchData2という2つの非同期関数を定義しています。

それぞれの関数は指定した時間(2秒と1秒)待機した後、文字列を返します。

そして、非同期のブロック内でこれらの関数を並行して実行するためのTaskを生成しています。

並行実行された結果は、各タスクの.valueプロパティを使用して取得できます。

上記のコードを実行すると、約2秒後に”Data1 Data2″という結果が出力されることが期待されます。

しかし、fetchData2は1秒で完了するため、実際には”Data2″が先に出力され、その後に”Data1″が出力される可能性が高いです。

○サンプルコード4:非同期タスクの直列実行

このコードでは、async/awaitを使って複数の非同期タスクを直列に実行する方法を表しています。

この例では、一つの非同期タスクが完了するのを待ってから、次の非同期タスクを実行しています。

import Swift

// 非同期関数の定義
func fetchDataA() async -> String {
    await Task.sleep(2 * 1_000_000_000) // 2秒待つ
    return "DataA"
}

func fetchDataB() async -> String {
    await Task.sleep(1 * 1_000_000_000) // 1秒待つ
    return "DataB"
}

// 直列実行の例
async {
    let dataA = await fetchDataA()
    let dataB = await fetchDataB()
    print(dataA, dataB)
}

上記のコードでは、fetchDataAfetchDataBという2つの非同期関数を定義しています。

それぞれの関数は指定した時間(2秒と1秒)待機した後、文字列を返します。

そして、非同期のブロッック内でこれらの関数を直列に実行しています。fetchDataAの完了を待ってから、fetchDataBが実行されます。

上記のコードを実行すると、約3秒後に”DataA DataB”という結果が出力されます。

この場合、”DataA”が先に出力され、その後に”DataB”が出力されることが保証されています。

○サンプルコード5:エラーハンドリングの実装

Swiftの非同期処理であるasync/awaitを使ってエラーハンドリングを実装する際のサンプルコードを紹介します。

このコードでは、非同期関数からのエラーを取得し、それに対する処理を行っています。

import Foundation

enum CustomError: Error {
    case sampleError
}

func asyncFunction() async throws -> Int {
    await Task.sleep(2 * 1_000_000_000)  // 2秒待機
    throw CustomError.sampleError
    return 100
}

func handleError() async {
    do {
        let result = try await asyncFunction()
        print(result)
    } catch CustomError.sampleError {
        print("エラーが発生しました。")
    } catch {
        print("不明なエラーです。")
    }
}

// 非同期関数の呼び出し
Task {
    await handleError()
}

このコードでは、asyncFunctionという非同期関数を作成しています。

この関数は2秒後にCustomError.sampleErrorをスローします。

handleError関数では、この非同期関数からエラーを捕捉して、それに応じた処理を行っています。

この例を実行すると、”エラーが発生しました。”と表示されます。

○サンプルコード6:UIの更新

Swiftの非同期処理において、UIの更新は非常に重要です。

特に、非同期でデータを取得した後、UIにそのデータを反映させる場面は多いです。

ここでは、非同期処理後にUIを更新するサンプルコードを紹介します。

まず、非同期処理でデータを取得し、その後UIを更新する例を紹介します。

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        Task {
            let data = await fetchData()
            updateUI(data: data)
        }
    }

    func fetchData() async -> String {
        await Task.sleep(2 * 1_000_000_000)  // 2秒待機
        return "非同期で取得したデータ"
    }

    func updateUI(data: String) {
        DispatchQueue.main.async {
            self.label.text = data
        }
    }
}

このコードでは、fetchDataという非同期関数を使ってデータを取得し、その後updateUI関数を使ってUIを更新しています。

fetchData関数は2秒待ってから文字列”非同期で取得したデータ”を返します。

この返り値をupdateUI関数でラベルにセットしています。

この例を実行すると、2秒待った後にラベルのテキストが”非同期で取得したデータ”に更新されます。

●async/awaitの注意点と対処法

Swiftの非同期処理機能、async/awaitは非常に便利な一方、使用する際に注意しなければならないポイントがいくつか存在します。

ここでは、async/awaitを使用する際の注意点と、それに対する対処法を解説します。

○注意点1:メインスレッドのブロッキング

Swiftでの非同期処理を行う際、メインスレッド上で重い処理を実行すると、アプリのUIがフリーズする問題が発生します。

これはasync/awaitを使用する場面でも同様で、特に注意が必要です。

対処法として、非同期処理を実行する際には、必ず別のスレッドで処理を行うようにしましょう。このようにすることで、メインスレッドがブロックされることなく、スムーズにUIを操作できるようになります。

○注意点2:キャンセル処理の実装

非同期処理は、ユーザーの操作やシステムの状況によって、途中でキャンセルする必要が出てくる場面が考えられます。

async/awaitを使用している場合も、キャンセル処理を適切に実装しなければなりません。

対処法として、非同期タスクをキャンセルする際には、Swiftにおけるキャンセルトークンを使用して、非同期処理の途中での中断を実現します。

○サンプルコード7:キャンセル処理の実装方法

このコードでは、Swiftの非同期処理をキャンセルしています。

この例では、非同期タスクを実行している途中でキャンセルするためのキャンセルトークンを使用しています。

import Foundation

// キャンセル可能な非同期タスクの定義
func asyncTask(cancelToken: CancellationToken) async -> Int {
    // ここでは仮に、5秒間の待機を行うとします。
    for _ in 0..<5 {
        // キャンセルトークンの確認
        if cancelToken.isCancelled {
            print("タスクがキャンセルされました。")
            throw CancellationError()
        }
        await Task.sleep(1_000_000_000)  // 1秒待つ
    }
    return 42  // タスクの結果
}

let cancelToken = CancellationToken()
// 3秒後に非同期タスクをキャンセル
Task {
    await Task.sleep(3_000_000_000)
    cancelToken.cancel()
}

// 非同期タスクの実行
do {
    let result = await asyncTask(cancelToken: cancelToken)
    print("タスクの結果: \(result)")
} catch {
    print("エラー: \(error)")
}

上記のサンプルコードを実行すると、非同期タスクが3秒後にキャンセルされ、”タスクがキャンセルされました。”というメッセージが表示されます。

このように、非同期処理の途中でのキャンセル処理を実装することができます。

キャンセルトークンは、非同期タスクがキャンセルされたかどうかを確認するためのもので、キャンセルされた場合は例外をスローしてタスクを中断します。

この方法を使用することで、非同期処理の途中でキャンセルが必要な場面にも柔軟に対応することができます。

●async/awaitのカスタマイズ方法

Swiftの非同期処理、特にasync/awaitにおけるカスタマイズ方法には多くの魅力的な手法が存在します。

Swiftのasync/await機能を更にパワフルにするためのカスタマイズを紹介いたします。

○カスタマイズ1:カスタム非同期関数の作成

非同期処理の中には、共通のパターンや繰り返しを使用する場面が出てきます。

そのため、カスタムの非同期関数を作成することで、コードの再利用性を高めることができます。

□サンプルコード8:カスタム非同期関数の実装

下記のコードでは、非同期的に数値を2倍にするシンプルな関数を作成しています。

import Foundation

func doubleNumberAsync(_ value: Int) async -> Int {
    await Task.sleep(1_000_000_000) // 1秒スリープ
    return value * 2
}

// 使用例
let number = 5
let doubled = await doubleNumberAsync(number)
print(doubled)

このコードではdoubleNumberAsync関数を使って、入力された数値を非同期的に2倍にしています。

この例では、5を入力として受け取り、1秒後に10を返す挙動となります。

もし上のコードを実行すると、コンソールには「10」と表示されます。

ただし、この間に他の処理を行いたい場合、awaitの後に続けて記述することで非同期的に進めることができます。

○カスタマイズ2:非同期処理のタイムアウト設定

非同期処理は、外部リソースへのアクセスや重たい計算など、時間がかかることがあります。

そのような場合、タイムアウトを設定することで、長時間待ち続けることなく、処理を中断することができます。

□サンプルコード9:タイムアウトの実装方法

Swiftには、非同期タスクにタイムアウトを設定するための独自の方法が提供されています。

ここではその一例として、非同期関数に3秒のタイムアウトを設定する方法を紹介します。

import Foundation

func fetchDataAsync() async -> String {
    await Task.sleep(5_000_000_000) // 5秒スリープ
    return "Data Fetched"
}

// タイムアウトを設定してデータ取得
async {
    do {
        let result = try await Task.withTimeout(seconds: 3) {
            await fetchDataAsync()
        }
        print(result)
    } catch {
        print("タイムアウトまたはエラーが発生しました。")
    }
}

このコードでは、fetchDataAsync関数は5秒かかる非同期処理を模倣しています。

しかし、Task.withTimeoutを使い、3秒のタイムアウトを設定しています。

したがって、3秒後にタイムアウトし、「タイムアウトまたはエラーが発生しました。」と表示されます。

このような実装は、特定の時間内に処理が完了しない場合に、適切なエラーメッセージをユーザーに通知する際に非常に役立ちます。

●非同期処理のデバッグ方法

非同期処理はその性質上、デバッグが複雑になることがあります。

Swiftのasync/awaitを使用すると、非同期処理をシンプルに扱うことができますが、それでもデバッグ時に気をつける点や知っておくと役立つデバッグ方法があります。

非同期処理のデバッグは、主に次の点に注意するとスムーズに進めることができます。

  1. 非同期タスクが完了したかどうかの確認
  2. エラーが発生した場合の対処方法
  3. 非同期タスクの実行順序の確認

このように、非同期タスクのデバッグは、タスクの完了状況やエラーの取り扱い、そしてタスクの実行順序に関することが中心となります。

○サンプルコード10:デバッグ時の非同期タスクの挙動確認

下記のサンプルコードでは、非同期タスクを実行して、その挙動をデバッグモードで確認する方法を表しています。

import Foundation

// 非同期関数の定義
func fetchData() async -> String {
    await Task.sleep(2 * 1_000_000_000)  // 2秒待機
    return "データ取得完了"
}

// 非同期関数の呼び出し
async {
    let result = await fetchData()
    print(result)
}

このコードでは、非同期関数fetchDataを用いてデータを取得する処理を行っています。

fetchData関数内では2秒の待機時間を持っており、その後に”データ取得完了”という文字列を返します。

非同期関数の呼び出し部分では、取得したデータをコンソールに出力します。

デバッグを行う際は、ブレークポイントを設定して、非同期タスクの進行状況を確認することができます。

具体的には、fetchData関数や非同期関数の呼び出し部分にブレークポイントを設定し、ステップ実行を行うことで、非同期処理の動きを詳しく確認することができます。

このサンプルコードを実行すると、コンソールに”データ取得完了”と表示されるのを確認することができます。

まとめ

Swiftの非同期処理の新機能、async/awaitは、開発者にとって非常に強力なツールとなっています。

従来の非同期処理手法と比較して、コードがシンプルかつ直感的に記述できるのが大きな魅力です。

この記事を通じて、async/awaitの基本的な使い方から応用例、さらにはデバッグ方法について詳しく解説しました。

特に、非同期処理のデバッグはその性質上難しさがありましたが、async/awaitを活用することで、その難しさを大幅に軽減することができます。

ブレークポイントを設定したり、ステップ実行を利用することで、非同期処理の動きを細かく追跡し、問題の特定や解決を効率的に行うことができるようになります。

Swift開発を行う際には、この新しい非同期処理機能を活用し、より品質の高いアプリケーションの開発を目指していきましょう。

非同期処理はコードの品質やパフォーマンスに直結する部分であるため、しっかりと理解しておくことが重要です。

非同期処理に関連する他の記事や情報も積極的に探求して、知識の幅を広げていくことをおすすめします。

技術は日々進化しているので、常に最新の情報を取り入れて、スキルアップを続けることが大切です。