【Swift Concurrency】async/awaitの使い方!非同期処理の実装

この記事からわかること
- Swift Concurrencyとは?
- async/awaitの使い方
- 非同期処理の実装方法
- completionHandler(コールバック関数)との違い
index
[open]
\ アプリをリリースしました /
環境
- Xcode:16.0
- iOS:18.0
- Swift:5.9
- macOS:Sonoma 14.6.1
公式リファレンス:Concurrency
公式ドキュメント:Concurrency
Swift Concurrencyとは?
Swift Concurrency(同時実効性)とはiOS15(Swift 5.5)から導入された仕組みの1つで非同期プログラミングをより利用しやすくするための機能を提供しています。async
やawait
キーワードはSwift Concurrencyから提供されており、非同期処理を実装する際に利用されるcompletionHandler(コールバック関数)の弱みである可読性の低下を解消することができるようになりました。
async/awaitの使い方
最初にasync
/await
キーワードの使い方とcompletionHandler(コールバック関数)を使用した場合との違いを見てみます。例えとして「サーバーからデータを取得する処理:fetchData」をそれぞれで実装してみます。
completionHandler(コールバック関数)
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
sleep(2)
let data = Data() // 本来ならサーバーからデータ取得(成功 or 失敗)
completion(.success(data))
}
print("START")
fetchData { result in
switch result {
case .success(let data):
print("Data received: \(data)")
case .failure(let error):
print("Error: \(error)")
}
}
おすすめ記事:【Swift】Result<Success,Failure>型の使い方!非同期のエラー処理
async/await
func fetchData() async throws -> Data {
sleep(2)
let data = Data() // 本来ならサーバーからデータ取得(成功 or 失敗)
return data
}
print("START")
Task {
do {
let data = try await fetchData()
print("Data received: \(data)")
} catch {
print("Error: \(error)")
}
}
あえてコードを長ったらしく書いた部分もありますが、コード量を抑えられて見通しが良くなったと思います。ではそれぞれの使い方を見ていきます。
asyncキーワード
async
キーワードは「非同期」の意味を持つ「Asynchronous:アシンクロナス」から来ています。このキーワードはthrows
キーワードと同様にパラメータの後に記述します。throws
キーワードも付与する場合はasync throws
の順に記述します。
async
キーワードのついた関数(メソッド)は非同期関数(メソッド)とみなされます。これはこの関数が非同期的に実行され、実行途中で中断される可能性を孕んだ特殊な種類の関数であることを示しています。
func fetchData() async throws -> Data { }
そして非同期関数(メソッド)を呼び出すためにはawait
キーワードが必要になります。
awaitキーワード
await
キーワードは非同期関数(メソッド)を呼び出していることをマークするためのキーワードです。このマークが付与されていると呼び出された非同期関数が完了する(返される)まで実行が一時停止されます(つまり直列処理になる)。そのため例えば以下のような場合は1が出力されてから2が出力されるのはfetchData
が完了した後の2秒後になります。
print("1")
let data = await fetchData() // 2秒かかる処理
print("2")
async
のついた関数をawait
つけずに呼び出そうとすると以下のようなエラーになります。
Expression is 'async' but is not marked with 'await'
またasync
がついた非同期関数をさらに別の関数から呼びだす場合はその関数にもasync
をつける必要があります。
func parentFunction() async {
print("1")
let data = await fetchData() // 2秒かかる処理
print("2") // fetchDataが完了した2秒後に呼び出される
}
つけ忘れると以下のエラーになります。
'async' call in a function that does not support concurrency
Task構造体
最終的に非同期関数を呼び出すにはTask
構造体にクロージャーとして渡す必要があります。先ほどの'async' call in a function that does not support concurrency
エラーが出ている場合にはasync
を付与するかTask
に渡すことで解決できます。
Task
は非同期作業の単位であり、非同期処理は1つのタスクの中で処理されます。Task.init
の定義にはクロージャーを受け取り、そのクロージャーにはasync
が付いています。
init(priority: TaskPriority?, operation: () async -> Success)
そのためTrailing Closure記法で省略してTask {}
と記述できます。
Task {
print("1")
let data = await fetchData() // 2秒かかる処理
print("2")
}
タスクインスタンスを作成後、クロージャーに渡した処理は即座に実行され、またこのインスタンスを使用してタスクの操作を行うことができるようになります。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。