こんにちは、こばやしよしのり @yoshiii514 です。
Swift5.5(iOS15、Xcode13)より、async/awaitが追加されました。
async/awaitは初学者には難しいテーマであるため、ここでは体験することを前提に、概要の解説をします。
async/awaitはSwiftで並行処理を実現しやすくすための仕組みです。並行処理とは複数の処理を同時に実行することです。
asyncは「エイシンク」、awaitは「アウェイト」と読みます。asyncは日本語で「非同期」を意味し、awaitは「待つ、待ち受ける」を意味します。
【目 次】
並行処理、同期、非同期について
並行処理
例えば、4コアのプロセッサを搭載したコンピュータでは、4つのコードを同時に実行し、各コアがそれぞれのタスクを実行します。これを並行処理といいます。この並行処理の実現には、同期・非同期の考え方が重要になります。
同期、非同期
同期、非同期については、次の書籍で解説がされています。もし、理解が不十分である方は参考にしてください。
- 書籍「たった2日でマスターできる iPhoneアプリ開発集中講座 Xcode13/iOS15/Swift5.5対応」
— P438:「COLUMN 同期処理と非同期処理」
今回、解説に使うプログラムコードについて
書籍「たった2日でマスターできる iPhoneアプリ開発集中講座 Xcode13/iOS15/Swift5.5対応」 で利用している、お菓子検索アプリのコードを使って、async/awaitについて体験します。コード全体の解説については、本書をご覧ください。ここでは、async/awaitについて解説をします。
Xcode Playgroundの利用
XcodeのPlaygroundを利用します。
▼Playgroundの起動
❶Xcodeメニューの[File]を選択します。
❷サブメニューの[New]を選択。
❸さらにサブメニューが表示されるので[Playgound…]を選択します。
▼Playgroundで利用するテンプレートの選択
Playgroundで利用するテンプレートを選びます。
❶[iOS]になっているこを確認してください。
❷[Blank]を選びます。
❸[Next]でテンプレートを選択します。
▼Playgroundファイルの作成
❶作成する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の実行
Playgroundはコードを入力すると自動実行されます。実行されるとデバッグエリアにprint文の文字が表示されます。もし、実行されていない場合は、上図の実行ボタンを押してください。
しばらくするとデバッグエリアにprint文で出力したテキストが表示されます。
コードの実行結果を確認
コードと実行結果を比較してみましょう。
プログラムコードは入力されている上から下に向かって実行されるほうがわかりやすいです。ですが、実際のprint文での出力結果をみると、実行されている順番が異なることがわかります。
わかりやすく整理すると、次のような処理のイメージになります。
同期のコード
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.すべての処理が終了")
コードの実行結果を確認
コードと実行結果を比較してみましょう。
同期コードの場合は、プログラムコードは入力されている順番とprint文での出力結果の順番が一致していることが確認できます。
わかりやすく整理すると、次のような処理のイメージになります。
従来の書き方ではわかりにくい
ここではセマフォを利用して同期処理を実現しています。
処理の順番は同期的になりましたが、次の点でわかりにくさが残ります。
・セマフォでの通知では、断続的になり処理の流れがわかりにくい
このことから、非同期的な処理を「コードが書いてある順番にプログラムを動かせる書き方」が必要になってきます。
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.4以前の非同期コードと比較すると、Swift5.5のasync/awaitでは、「コードが書いてある順番にプログラムが実行されるので、とてもわかりやすい」のが理解できると思います。
async/await関連の用語解説
Task:タスク
Taskは、非同期の作業の単位です。「Task{}」でコードをグループ化するイメージです。
Task {
(実行するコードの集まり)
}
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アプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!
まずは、現役エンジニアに相談できる無料相談をご利用ください。