Async/Awaitは、同期的なスタイルで非同期コードを記述するためのキーワードのペアで、SwiftではiOS 13以降、Kotlinではコルーチンで利用できます。Apple Swift Documentation, 2026によると、Async/AwaitはコールバックチェーンとGCDを置き換え、非同期コードを線形で読みやすくします。asyncキーワードは関数を非同期としてマークし、awaitは結果を受け取るまでその実行を一時停止します。
主要ポイント
Async/Awaitは、同期コードと同じくらい線形的に非同期コードを記述できる言語構造です。SwiftではiOS 13 / macOS 10.15でSwift Concurrencyフレームワークとともに登場しました。Kotlinでは、バージョン1.3以降、コルーチンライブラリ(kotlinx.coroutines)を通じてasync/awaitが利用可能です。
async/await以前は、非同期コードはコールバック、GCD、またはRxSwiftに基づいて構築されていました。ネストされたコールバックの連鎖はコールバック地獄を引き起こし、深いネストが可読性とデバッグを困難にしました。Async/Awaitは、非同期関数がawaitポイントで“一時停止”し、結果を受け取った後に再開できるようにすることでこの問題を解決します。
Google(2025年)のデータによると、Kotlinでasync/awaitを使用すると、コールバックアプローチと比較して非同期コードの行数が40–60%削減されます。Swiftでは、2025年に公開されたアプリケーションの間でSwift Concurrencyの採用が68%に達しました。iOS 16はUIKit、SwiftUI、FoundationにSwift Concurrencyのサポートを追加し、async/awaitをすべての新しいAppleプロジェクトの標準ツールにしました。
async/awaitとスレッドの主な違いはブロッキングなしの一時停止です。関数がawaitに遭遇すると、一時停止して現在のスレッドを他のタスクに解放します。非同期操作が完了すると、関数は同じまたは別のスレッドで再開されます。これを協調的マルチタスクと呼びます。
非同期関数はステートマシンにコンパイルされます。これは実行状態を管理する有限オートマトンです。awaitポイントで、コンパイラはコンテキスト(ローカル変数、戻りアドレス)を保存し、制御をランタイムに渡します。結果が準備できると、ランタイムがコンテキストを復元して実行を続行します。
Swiftでは、このステートマシンはSIL(Swift Intermediate Language)コンパイラレベルで実装されます。Kotlinでは、Continuation Passing Style(CPS)メカニズムが使用されます。各suspend関数は、結果が返される隠れたContinuationパラメータを受け取ります。どちらのアプローチも、メモリリークなしで安全な再開を保証します。
コールバックアプローチでは、操作の完了後に呼び出されるクロージャを渡す必要があります。3つの逐次リクエストがある場合、各レベルでエラー処理を伴う3レベルのネストが発生します。Async/Awaitは同じ3つのリクエストを、単一のcatchブロックを持つ3行の逐次コードに変換します。
GCD(DispatchQueue)はシリアルキューとDispatchGroupを通じてコールバック地獄を解決しますが、依然として冗長です。単純なタスク(ユーザーの読み込み、次に友達、次にUI)には、キュー、グループ、notifyブロックの作成が必要です。Async/Awaitは単一の関数内で3行で同じタスクを実行します。
| 側面 | コールバック | GCD | Async/Await |
|---|---|---|---|
| ネスト | 深い(破滅のピラミッド) | 中程度(グループ+通知) | 線形(フラット) |
| エラー処理 | コールバックごと | 手動 | 統一(try/catch) |
| キャンセル | 手動 | 制限あり | Task.cancel() |
| スレッドセーフ | 手動 | シリアルキュー経由 | MainActor経由 |
Apple WWDC 2024によると、Swift Concurrency async/awaitは新しいコードに推奨されるアプローチです。GCDはCライブラリとの統合や特定の低レベルスレッド処理シナリオのために残っています。
TaskはSwiftにおける非同期作業の基本単位です。Taskは既存のアクターのコンテキスト内または任意のスレッド上で作成されます。Task内では、awaitを通じて非同期関数を呼び出すことができます。TaskGroupは複数の子タスクを並行して起動し、その結果を収集できます。これはDispatchGroupの強化版です。
Actorは、データ競合から状態を保護するスレッドセーフな参照型です。コンパイラは、アクターのプロパティへのアクセスが非同期呼び出しまたはアクターの分離内でのみ可能であることを保証します。Actorは、手動ロックなしで共有可変状態を保護するためのシリアルDispatchQueueを置き換えます。
Structured Concurrencyの原則は次の通りです。すべての非同期タスクには親があり、親はすべての子が完了するまで完了しません。タスクAがTask { await B() }を起動すると、AはBを待ちます。これによりタスクリークが防止され、予測可能なライフサイクルが保証されます。
Kotlinでは、async/awaitはコルーチンを通じて実装されます。コルーチンは言語の軽量スレッドです。関数はsuspendキーワード(Swiftのasyncに類似)でマークされます。起動にはlaunch(ファイアアンドフォーゲット)またはasync(結果あり)が使用されます。結果を取得するにはawaitを呼び出します。
Dispatcherは、コルーチンが実行されるスレッドプールを決定します。UIにはDispatchers.Main、ネットワーク/ディスクにはDispatchers.IO、CPU負荷の高いタスクにはDispatchers.Defaultです。Structured ConcurrencyはCoroutineScopeを通じて保証されます。スコープがキャンセルされると、すべての子コルーチンがキャンセルされます。
JetBrains(2025年)によると、Google PlayのAndroidアプリの97%がkotlinx.coroutinesを使用しており、その82%がネットワークリクエストにasync/awaitを採用しています。コルーチンはAndroidの非同期プログラミングのデファクトスタンダードになりました。Kotlin Multiplatformもコルーチンをサポートしており、Android、iOS、サーバーサイドで共有の非同期コードを使用できます。
各言語の2つの例を見てみましょう。逐次データ読み込みと、TaskGroupまたはasync/awaitを使用した並行リクエストです。
awaitはユーザーデータを受け取るまで、次に友達データを受け取るまで実行を一時停止します。ネストされたクロージャはありません。
func loadProfile() async throws -> Profile {
let user = try await api.fetchUser()
let friends = try await api.fetchFriends(for: user.id)
return Profile(user: user, friends: friends)
}
TaskGroupはタスクを並行して起動し、結果を配列に収集します。結果の順序は起動の順序と一致しない場合があります。
func loadParallel() async throws -> [String] {
await withThrowingTaskGroup(of: String.self) { group in
group.addTask { try await api.fetchName() }
group.addTask { try await api.fetchEmail() }
group.addTask { try await api.fetchAvatar() }
var results = [String]()
for try await value in group { results.append(value) }
return results
}
}
suspend関数は他のsuspend関数を呼び出すことができます。Swiftに類似:逐次呼び出し。
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
Kotlinでは、各リクエストにasyncを使用したcoroutineScopeを通じて並行性が実現されます。
suspend fun loadParallel(): List<String> = coroutineScope {
val name = async { api.fetchName() }
val email = async { api.fetchEmail() }
val avatar = async { api.fetchAvatar() }
listOf(name.await(), email.await(), avatar.await())
}
Task { }の忘れ — 非同期関数はTaskでラップせずに同期コンテキストから呼び出すことはできません。Swiftでは、これはエラーとしてコンパイルされます。Kotlinでは、lifecycleScopeまたはviewModelScopeを通じて起動する必要があります。通常の関数からsuspend関数を呼び出そうとするとコンパイルされません。
すべての関数を非同期にする必要はありません。I/OのないCPU負荷の高いタスクはasyncの恩恵を受けません。.userInitiated QoSのDispatchQueueで実行する方が適しています。Async/AwaitはI/Oバウンドの操作(ネットワーク、ディスク、待機)に最適化されています。
メインスレッドで.resultを使用する(非同期関数の結果を同期的に待機する)とブロッキングが発生します。Swiftでは、Task.synchronousWaitはAppleによって推奨されていません。Kotlinでは、メインスレッドでのrunBlockingはアンチパターンです。lifecycleScopeを使用してください。
Taskがクラスのプロパティに保存されていない場合、コンテキストを離れるとキャンセルされます。Swiftでは、Taskはスコープ化されており、そのライフサイクルは作成元のコンテキストに結びついています。長時間実行される操作が必要な場合は、Taskをプロパティに保存してください。
よくある質問
Async/Awaitはキューを作成する必要のない言語構造です。DispatchQueueはスレッドを管理するためのシステムAPIです。Async/awaitは言語レベルでステートマシンにコンパイルされ、GCDはOSレベルでシステムコールにコンパイルされます。
いいえ — awaitは関数を一時停止しますが、他のタスクのためにスレッドを解放します。操作が完了すると、関数はプール内の利用可能な任意のスレッドで再開されます。
はい — SwiftではTask.cancel()、KotlinではJob.cancel()を使用します。キャンセルはStructured Concurrencyに従ってすべての子タスクに伝播されます。コードはTask.isCancelledまたはensureActive()を通じてキャンセルフラグを確認します。
MainActorは、そのプロパティとメソッドが常にメインスレッドで実行されるアクターです。関数を@MainActorでマークすることで、UIの更新が正しいスレッドで行われることを保証し、DispatchQueue.main.asyncを置き換えます。
Async/AwaitはSwift(iOS 13+)、Kotlin(kotlinx.coroutines経由)、Dart(Flutter)、JavaScript/TypeScript、Python、C#、Rust、Go(構文糖を持つgoroutines経由)で利用可能です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。