Swift5.5で追加された「async/await」を体験して理解を深めよう

Swift5.5で追加された「async/await」を体験して理解を深めよう

こんにちは、こばやしよしのり @yoshiii514 です。

Swift5.5(iOS15、Xcode13)より、async/awaitが追加されました。
async/awaitは初学者には難しいテーマであるため、ここでは体験することを前提に、概要の解説をします。
async/awaitはSwiftで並行処理を実現しやすくすための仕組みです。並行処理とは複数の処理を同時に実行することです。
asyncは「エイシンク」、awaitは「アウェイト」と読みます。asyncは日本語で「非同期」を意味し、awaitは「待つ、待ち受ける」を意味します。

並行処理、同期、非同期について

並行処理

例えば、4コアのプロセッサを搭載したコンピュータでは、4つのコードを同時に実行し、各コアがそれぞれのタスクを実行します。これを並行処理といいます。この並行処理の実現には、同期・非同期の考え方が重要になります。

同期、非同期

同期、非同期については、次の書籍で解説がされています。もし、理解が不十分である方は参考にしてください。

今回、解説に使うプログラムコードについて

書籍「たった2日でマスターできる iPhoneアプリ開発集中講座 Xcode13/iOS15/Swift5.5対応」 で利用している、お菓子検索アプリのコードを使って、async/awaitについて体験します。コード全体の解説については、本書をご覧ください。ここでは、async/awaitについて解説をします。

Xcode Playgroundの利用

XcodeのPlaygroundを利用します。

▼Playgroundの起動
Swift5.5で追加された「async/await」を体験して理解を深めよう

❶Xcodeメニューの[File]を選択します。
❷サブメニューの[New]を選択。
❸さらにサブメニューが表示されるので[Playgound…]を選択します。

▼Playgroundで利用するテンプレートの選択
Swift5.5で追加された「async/await」を体験して理解を深めよう

Playgroundで利用するテンプレートを選びます。
❶[iOS]になっているこを確認してください。
❷[Blank]を選びます。
❸[Next]でテンプレートを選択します。

▼Playgroundファイルの作成
Swift5.5で追加された「async/await」を体験して理解を深めよう

❶作成するPlaygroundファイル名を入力します。
❷保存するフォルダを指定します。
❸[Create]ボタンを押し、ファイルを作成します。

Swift5.4までのプログラムコードで確認

まずは、「async/await」が使えなかった、Swift5.4でのコードで確認します。
最初は非同期のコードを書き、その次に同期に対応したコードを書きます。

非同期のコード

新規で作成した、Playgroundファイル名に、次のコードを追加しましょう。


import Foundation

let keyword = "カレー味"
let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
// リクエストURLの組み立て
let req_url = URL(string: "https://sysbird.jp/toriko/api/?apikey=guest&format=json&keyword=\(keyword_encode!)&max=10&order=r")!

print("1.データの取得を開始します")

// リクエストするタスクを作成
let task = URLSession.shared.dataTask(with: req_url) { data, response, error in
    print("2.データが取得できた") 
}
// タスクの実行
task.resume()

print("3.すべての処理が終了")

Playgroundの実行

XcodePlaygroundの実行

Playgroundはコードを入力すると自動実行されます。実行されるとデバッグエリアにprint文の文字が表示されます。もし、実行されていない場合は、上図の実行ボタンを押してください。
しばらくするとデバッグエリアにprint文で出力したテキストが表示されます。

コードの実行結果を確認

コードと実行結果を比較してみましょう。

Swift5.4非同期的なAPIリクエスト

プログラムコードは入力されている上から下に向かって実行されるほうがわかりやすいです。ですが、実際のprint文での出力結果をみると、実行されている順番が異なることがわかります。

注意
非同期のコードは、コードが書かれている順番と実際に動いている順番が違うので、「わかりにくい」「テストが難しい」ということになります。

わかりやすく整理すると、次のような処理のイメージになります。
Swift5.4非同期的なAPIリクエスト

同期のコード

Swift5.4以前でも、非同期のコードを同期的に書くことができます。

新規で作成した、Playgroundファイル名に、次のコードを追加しましょう。


import Foundation

let keyword = "カレー味"
let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
// リクエストURLの組み立て
let req_url = URL(string: "https://sysbird.jp/toriko/api/?apikey=guest&format=json&keyword=\(keyword_encode!)&max=10&order=r")!

/// セマフォ
var semaphore : DispatchSemaphore!
semaphore = DispatchSemaphore(value: 0)

print("1.データの取得を開始します")

// リクエストするタスクを作成
let task = URLSession.shared.dataTask(with: req_url) { data, response, error in
    
    print("2.データが取得できた") 
    // 処理が完了したことを通知
    semaphore.signal()
}
// タスクの実行
task.resume()

// semaphore.signal()が呼び出されるまで待機する
semaphore.wait()
print("3.すべての処理が終了")

コードの実行結果を確認

コードと実行結果を比較してみましょう。

Swift5.4semaphoreセマフォによる同期APIコード

同期コードの場合は、プログラムコードは入力されている順番とprint文での出力結果の順番が一致していることが確認できます。

わかりやすく整理すると、次のような処理のイメージになります。
同期的なAPIリクエスト

従来の書き方ではわかりにくい

ここではセマフォを利用して同期処理を実現しています。

セマフォとは
コンピュータで並列処理を行う際、同時に実行されているプログラム間で資源(リソース)の排他制御や同期を行う仕組みの一つ。 当該資源のうち現在利用可能な数を表す値のこと。

Swift同期セマフォ

処理の順番は同期的になりましたが、次の点でわかりにくさが残ります。

・task.resumeで実行するタスク(コード)は、先に書いておく必要があるので、処理が戻るイメージ
・セマフォでの通知では、断続的になり処理の流れがわかりにくい

このことから、非同期的な処理を「コードが書いてある順番にプログラムを動かせる書き方」が必要になってきます。

Swift5.5から使えるasync/await

新規で作成した、Playgroundファイル名に、次のコードを追加しましょう。
このコードは、Swift5.5から使えるasync/awaitを利用したコードになります。


import SwiftUI

let keyword = "カレー味"
let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
// リクエストURLの組み立て
let req_url = URL(string: "https://sysbird.jp/toriko/api/?apikey=guest&format=json&keyword=\(keyword_encode)&max=10&order=r")!

Task {
    print("1.データの取得を開始します") 
    
    // リクエストURLからダウンロード
    let (data , response) = try await URLSession.shared.data(from: req_url)
    // データ取得に成功
    if (response as? HTTPURLResponse)?.statusCode == 200 {
        print("2.データの取得できた")         
    }
    
    print("3.すべての処理が終了")
}

コードの実行結果を確認

コードと実行結果を比較してみましょう。

Swift5.5で追加された「async/await」を体験して理解を深めよう

Swift5.4以前の非同期コードと比較すると、Swift5.5のasync/awaitでは、「コードが書いてある順番にプログラムが実行されるので、とてもわかりやすい」のが理解できると思います。

async/await関連の用語解説

Task:タスク

Taskは、非同期の作業の単位です。「Task{}」でコードをグループ化するイメージです。


Task {
  (実行するコードの集まり)
}

Task:タスク、並行処理、Swift

Task全体では、非同期として実行します。
Taskでグループ化することで、このTaskの完了を待つことができたり、Taskをキャンセルすることができます。
Taskの中では、awaitを利用することで、非同期な処理の完了をまって、次の処理を実行することができます。そのため、Taskでグループ化された処理は、同期的に実行することができます。

async:エイシンク

asyncは日本語で「非同期」を意味します。

今回はTaskを使い、その中でコードを書いたのでasyncは登場していません。

関数やイニシャライザを使うときは、非同期関数であることをマーク(非同期関数の型であることをマーク)する必要があります。このときにasyncを付加します。
例えば、次のように利用します。


func getData() async throws {
    print("1.データの取得を開始します") 
    
    // リクエストURLからダウンロード
    let (_ , response) = try await URLSession.shared.data(from: req_url)
    // データ取得に成功
    if (response as? HTTPURLResponse)?.statusCode == 200 {
        print("2.データの取得できた")         
    }
    
    print("3.すべての処理が終了")
}
Task {
    await try getData()
}

await:アウェイト

awaitは、日本語で「待つ、待ち受ける」を意味します。

非同期の処理が完了するまで、今の処理(非同期の呼び出し側)を中断するために、非同期の処理をマークするために付加します。そして、非同期の処理が完了したら、次のコードから実行を再開します。


// リクエストURLからダウンロード
let (_ , response) = try await URLSession.shared.data(from: req_url)
// データ取得が完了したら、次のコードを実行
if (response as? HTTPURLResponse)?.statusCode == 200 {
    print("2.データの取得できた")         
}

awaitは、Taskやasyncとセットで利用されます。

アプリ開発が学べる勉強会を開催中!
プログラミング初心者向け、アプリ開発ノウハウ、エンジニアキャリアについてのイベントを開催!アプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。
 グループのメンバーとして参加する
徹底した基礎学習からのマスターするiPhoneアプリ開発集中オンライン講座開講!
徹底した基礎学習からのマスターするiPhoneアプリ開発集中オンライン講座開講!
本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!
まずは、現役エンジニアに相談できる無料相談をご利用ください。