はじめに
swiftのasync/awaitについて調べてみたので記事にしようと思います。
async/awaitとは
- 非同期処理を同期的に書けるもののようです。swift5.5から導入されました。
コールバックとの違い
- Dataを取得する関数をコールバックで書いてそれを使用する処理は以下のようになります。
コールバック
func fetch(completion: @escaping (Data) -> Void)
fetch { data in
// dataを使う処理
}
- async/awaitで書くと以下のようになります。
async/await
func fetch() async -> Data
let data = await fetch()
// dataを使う処理
- async/awaitで書く時は戻り値で結果を受け取ります。
- 引数の後にasyncを書きます。
- 使う時はasyncに対してawaitを書いて受け取ります。
エラーをハンドリングする時
- エラーハンドリングする時のコールバックとasync/awaitの比較をしてみます。
コールバック
func fetch(completion: @escaping (Result<Data, Error>) -> Void)
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
fetch { result in
switch result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(error))
}
}
}
- async/awaitで書くと以下のようになります。
async/await
func fetch() async throws -> Data
func fetchData() async throws -> Data {
let data = try await fetch()
return data
}
- throwsのおかげでエラーハンドリングを書く必要がなくなりました。
より複雑な時
- たまにクロージャが2重になってネストが深くなったりすることがあります。
コールバック
func fetch(id: Int, completion: @escaping (Result<Int, Error>) -> Void)
func fetchData(id: Int, completion: @escaping (Result<Int, Error>) -> Void) {
fetch(id: 1) { result in
switch result {
case .success(let number):
fetch(id: number) { result in
switch result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
- async/awaitにしてみると
async/await
func fetch(id: Int) async throws -> Int
func fetchData() async throws -> Int {
let number = try await fetch(id: 1)
let data = try await fetch(id: number)
return data
}
- かなりスッキリしました。
呼び出し
- asyncのついた処理を呼び出すときはTaskで囲います
Task {
let data = await fetchData()
}
とか
Task {
do {
let data = try await fetchData()
} catch {
//エラーハンドリング
}
}
とか
コールバックからasync/awaitに変換する
- 既存のコールバックの関数からasync/awaitに変換することもできます。
- 今までコールバックで書いていた関数やコールバックで書かれたライブラリの関数を使うときに便利です。
- 例えば以下のようなコールバック関数があるとします。
func fetch(completion: @escaping (Result<Data, Error>) -> Void)
これをasync/awaitを使って呼び出します。
func fetchData() async throws -> Data {
try await withCheckedThrowingContinuation({ continuation in
fetch { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
})
}
- withCheckedThrowingContinuationを使うとcontinuation.resume(returning: data)で戻り値として結果を返し、continuation.resume(throwing: error)でエラーを投げてくれます。
- 結果がResultの時は以下のようにも書けます
func fetchData() async throws -> Data {
try await withCheckedThrowingContinuation({ continuation in
fetch { result in
continuation.resume(with: result)
}
})
}
- 個人的にはこちらの方が使うことが多そうです。
最後に
まだまだ勉強は必要そうですが、とても便利そうなのでこれから使っていけたらと思います!!
おかしなところがあればコメントで教えていただけると幸いでございます。