Swiftで async と await を使う方法を例題で紹介します。 async と await はSwift 5.5(iOS 15以上) の新しいキーワードで、非同期プログラミングの実装を支援します。
重要: この記事を投稿したのが2021年10月12日(火)で自身の法人サイトの方でしたけれども、公式サイトの更新などでブログ系のメンテナンスが大変だったので、ここに過去の投稿の移動とこれからの記事を投稿をする予定です。
リッチなiOSやmacOSのエクスペリエンスでは、リモートサーバからデータを取得するような非同期タスクがしばしば実行されます。Swift 5.5 の新機能である async と await キーワードによって、開発者は非同期コードを簡略化し、非同期タスクを簡単に実装できるようになります。この記事では、asyncとawaitの概要を例を挙げて紹介します。
目次
- Async(非同期)
a. Async Throws
b. Asyncの戻り値 - Await(待機)
a. Await Throws - Async Let(非同期定数)
a. Async Await Let - Async/Awaitの例
a. Await(同期)コードからAsync(非同期)メソッドを呼び出す
b. Async Await対クロージャの比較
1. Async (非同期)
以前のバージョンのSwiftでは、非同期プログラミングは補完ブロックを使って実装されることが多かったです。
// saveChangesが非同期ロジックを完了すると、完了ブロックが呼び出されます。
func saveChanges(completion: (() -> Void)?) { ... }
// 他のコードでは、saveChanges関数は次のように呼び出されます。
saveChanges {
// ハンドルの完成
}
新しい async キーワードを使用して、Swift は非同期ロジックを持つものとして関数をマークすることができます。
// SwiftはsaveChangesの非同期完了を自動的に処理する
func saveChanges() async { ... }
// 他のコードでは、async saveChanges 関数は次のように呼び出されます。
await saveChanges()
a. Async Throws
以前のバージョンのSwiftでは、非同期プログラミングは補完ブロックを使って実装されることが多かったです。
func attachImageToData(
imageResponse: URLResponse,
dataResponse: URLResponse,
completion: ((Error?) -> Void)?
)
async を使用すると、attachImageToData
関数でthrows
を使用できるようになり、他の非同期関数がエラーに使用する構文と一致します。
func attachImageToData(
imageResponse: URLResponse,
dataResponse: URLResponse) async throws
b. Asyncの戻り値
Swiftの以前のバージョンでは、いくつかの完了ブロックは、潜在的なエラーに加えて、他のタイプを含んでいました。これらの他のタイプは、しばしば論理的に非同期ロジックの戻り値のタイプでした。
func uploadImage(
image: UIImage,
completion: ((URLResponse?, Error?) -> Void)?
)
func uploadData(
data: Data,
completion: ((URLResponse?, Error?) -> Void)?
)
asyncを使用することで、uploadImage
とuploadData
の関数は、期待される戻り値の型定義 -> URLResponse
を利用し、他の非同期関数の構文と一致させることができます。
func uploadImage(image: UIImage)
async throws -> URLResponse
func uploadData(data: Data)
async throws -> URLResponse
2. Await (待機)
await キーワードは、非同期関数の完了を待つように Swift に指示するために使用されます。
// saveChangesの完了を待つ
await saveChanges()
await を使って複数のasync関数が呼び出された場合、Swift は次の関数に移る前に各関数の完了を待ちます。
// uploadDataの完了を待ってsaveChangesに移行する。
await uploadData(data: Data())
// saveChangesの完了を待って、次に進む。
await saveChanges()
a. Await Throws
throw できる非同期関数と同様に、tryも await と組み合わせて非同期関数に使用し、非同期関数を呼び出してエラーが発生した場合に throw することができます。
try await attachImageToData(
imageResponse: imageResponse,
dataResponse: dataResponse
)
3. Async Let (非同期定数)
非同期ロジックでは、async関数からの戻り値が必要な場合があります。async let構文を使って let変数を非同期としてマークすることができます。つまり、let変数は非同期ロジックが完了した後に利用可能になります。
async let imageResponse = try uploadImage(
image: image
)
a. Async Await Let
複数の async let文を組み合わせることで、Swiftは非同期ロジックを並列に実行することができるようになります。
// BimageResponseとdataResponseの両方が並行して取得される
async let imageResponse = try uploadImage(
image: image
)
async let dataResponse = try uploadData(
data: data
)
以前の async let変数の完了を待つには、let変数が初期化されていることが必要なときにawait を使用します。
try await attachImageToData(
imageResponse: await imageResponse,
dataResponse: await dataResponse
)
あるいは、await を使って並行処理を制御し、一度に一つの値しか取得しないようにします。
// await を使うと、uploadImage が先に呼ばれるようになります。
let imageResponse = try await uploadImage(
image: image
)
// uploadImageに `await`を使うことで、imageResponseが
// 初期化された後にuploadDataが呼ばれます。
let dataResponse = try await uploadData(
data: data
)
4. Async/Awaitの例
この記事で紹介する async/await の例では、以下のasync関数を参照します。
func uploadImage(image: UIImage)
async -> URLResponse
func uploadData(data: Data)
async throws -> URLResponse
func attachImageToData(
imageResponse: URLResponse,
dataResponse: URLResponse) async throws
func saveChanges() async throws
複数の非同期呼び出しと戻り値を組み合わせたasync/await の例。
// 複数の`async`関数呼び出しを含む非同期関数uploadTaskを定義する。
func uploadTask(
image: UIImage,
data: Data) async throws {
// 以下の非同期関数を同時に実行する。
async let imageResponse = try uploadImage(
image: image
)
async let dataResponse = try uploadData(
data: data
)
// attachImageToDataの完了を待って、次に進む。
try await attachImageToData(
imageResponse: await imageResponse,
dataResponse: await dataResponse
)
// saveChangesの完了を待って、次に進みます。
try await saveChanges()
}
a. Await(同期)コードからAsync(非同期)メソッドを呼び出す
同期コードから非同期関数を呼び出すために、タスクを初期化します。
func userTappedUpload() {
Task(priority: .default) {
do {
try await self.upload(
image: UIImage(),
data: Data()
)
}
catch {
// Handle error
}
}
}
b. Async Await対クロージャの比較
asyncとawait を使用することの1つの利点は、Swiftのコードを読みやすくすることです。この記事で紹介した async 関数 uploadTask
を補完クロージャのみを使用するように書き直した場合、この関数は読みにくい深いネストしたコールバック構造を持っています。
func uploadTask(
image: UIImage,
data: Data,
completion: ((Error?) -> Void)?) {
uploadImage(image: image) { imageResponse in
// imageResponseが成功したことを確認する
self.uploadData(data: data) {
dataResponse, dataError in
// もし、`dataError != nil`なら`dataError`を処理する
self.attachImageToData(
imageResponse: imageResponse!,
dataResponse: dataResponse!) {
attachError in
// もし、`attachError != nil`なら`Handle attachError`を処理する
self.saveChanges() {
completion?(nil)
}
}
}
}
}
// アップロードの呼び出し例
upload(image: UIImage(), data: Data()) { error in
if let error = error {
// エラーハンドラー
}
}
Swiftによる非同期プログラミング
以上です。asyncとawait を使うことで、コールバックによる非同期タスクと非同期ロジックをSwiftで実装することができます。