【Swift】Async / Awaitについて

Swiftで <font color="#C93E3F">**async**</font> と <font color="#C93E3F">**await**</font> を使う方法を例題で紹介します。 <font color="#C93E3F">**async**</font> と <font color="#C93E3F">**await**</font> はSwift 5.5（iOS 15以上） の新しいキーワードで、非同期プログラミングの実装を支援します。

:::note warn
重要： この記事を投稿したのが2021年10月12日（火）で自身の法人サイトの方でしたけれども、公式サイトの更新などでブログ系のメンテナンスが大変だったので、ここに過去の投稿の移動とこれからの記事を投稿をする予定です。
:::

リッチなiOSやmacOSのエクスペリエンスでは、リモートサーバからデータを取得するような非同期タスクがしばしば実行されます。Swift 5.5 の新機能である <font color="#C93E3F">**async**</font> と <font color="#C93E3F">**await**</font> キーワードによって、開発者は非同期コードを簡略化し、非同期タスクを簡単に実装できるようになります。この記事では、asyncとawaitの概要を例を挙げて紹介します。

## 目次

1. Async（非同期）
    a. Async　Throws
    b. Asyncの戻り値
2. Await（待機）
    a. Await Throws
3. Async Let（非同期定数）
    a. Async Await Let
4. Async/Awaitの例
    a. Await（同期）コードからAsync（非同期）メソッドを呼び出す
    b. Async Await対クロージャの比較

## 1. Async （非同期）
以前のバージョンのSwiftでは、非同期プログラミングは補完ブロックを使って実装されることが多かったです。

```swift
// saveChangesが非同期ロジックを完了すると、完了ブロックが呼び出されます。
func saveChanges(completion: (() -> Void)?) { ... }

// 他のコードでは、saveChanges関数は次のように呼び出されます。
saveChanges {
    // ハンドルの完成
}
```

新しい <font color="#C93E3F">**async**</font> キーワードを使用して、Swift は非同期ロジックを持つものとして関数をマークすることができます。

```swift
// SwiftはsaveChangesの非同期完了を自動的に処理する
func saveChanges() async { ... }

// 他のコードでは、async saveChanges 関数は次のように呼び出されます。
await saveChanges()
```

### a. Async Throws
以前のバージョンのSwiftでは、非同期プログラミングは補完ブロックを使って実装されることが多かったです。

```swift
func attachImageToData(
    imageResponse: URLResponse, 
    dataResponse: URLResponse, 
    completion: ((Error?) -> Void)?
)
```

<font color="#C93E3F">**async**</font> を使用すると、`attachImageToData`関数で`throws`を使用できるようになり、他の非同期関数がエラーに使用する構文と一致します。

```swift
func attachImageToData(
    imageResponse: URLResponse,
    dataResponse: URLResponse) async throws
```

### b. Asyncの戻り値
Swiftの以前のバージョンでは、いくつかの完了ブロックは、潜在的なエラーに加えて、他のタイプを含んでいました。これらの他のタイプは、しばしば論理的に非同期ロジックの戻り値のタイプでした。

```swift
func uploadImage(
    image: UIImage, 
    completion: ((URLResponse?, Error?) -> Void)?
)

func uploadData(
    data: Data, 
    completion: ((URLResponse?, Error?) -> Void)?
)
```

<font color="#C93E3F">**async**</font>を使用することで、`uploadImage`と`uploadData`の関数は、期待される戻り値の型定義 `-> URLResponse`を利用し、他の非同期関数の構文と一致させることができます。

```swift
func uploadImage(image: UIImage) 
    async throws -> URLResponse
    
func uploadData(data: Data)
    async throws -> URLResponse
```

## 2. Await （待機）
<font color="#C93E3F">**await**</font> キーワードは、非同期関数の完了を待つように Swift に指示するために使用されます。

```swift
// saveChangesの完了を待つ
await saveChanges()
```

<font color="#C93E3F">**await**</font> を使って複数の<font color="#C93E3F">**async**</font>関数が呼び出された場合、Swift は次の関数に移る前に各関数の完了を待ちます。

```swift
// uploadDataの完了を待ってsaveChangesに移行する。
await uploadData(data: Data())

// saveChangesの完了を待って、次に進む。
await saveChanges()
```

### a. Await Throws
<font color="#C93E3F">**throw**</font> できる非同期関数と同様に、<font color="#C93E3F">**try**</font>も <font color="#C93E3F">**await**</font> と組み合わせて非同期関数に使用し、非同期関数を呼び出してエラーが発生した場合に <font color="#C93E3F">**throw**</font> することができます。

```swift
try await attachImageToData(
    imageResponse: imageResponse, 
    dataResponse: dataResponse
)
```

## 3. Async Let （非同期定数）
非同期ロジックでは、<font color="#C93E3F">**async**</font>関数からの戻り値が必要な場合があります。<font color="#C93E3F">**async let**</font>構文を使って <font color="#C93E3F">**let**</font>変数を非同期としてマークすることができます。つまり、<font color="#C93E3F">**let**</font>変数は非同期ロジックが完了した後に利用可能になります。

```swift
async let imageResponse = try uploadImage(
    image: image
)
```

### a. Async Await Let
複数の <font color="#C93E3F">**async let**</font>文を組み合わせることで、Swiftは非同期ロジックを並列に実行することができるようになります。

```swift
// BimageResponseとdataResponseの両方が並行して取得される
async let imageResponse = try uploadImage(
    image: image
)

async let dataResponse = try uploadData(
    data: data
)
```

以前の <font color="#C93E3F">**async let**</font>変数の完了を待つには、<font color="#C93E3F">**let**</font>変数が初期化されていることが必要なときに<font color="#C93E3F">**await**</font> を使用します。

```swift
try await attachImageToData(
    imageResponse: await imageResponse, 
    dataResponse: await dataResponse
)
```

あるいは、<font color="#C93E3F">**await**</font> を使って並行処理を制御し、一度に一つの値しか取得しないようにします。

```swift
// await を使うと、uploadImage が先に呼ばれるようになります。
let imageResponse = try await uploadImage(
    image: image
)

// uploadImageに `await`を使うことで、imageResponseが
// 初期化された後にuploadDataが呼ばれます。
let dataResponse = try await uploadData(
    data: data
)
```

## 4. Async/Awaitの例
この記事で紹介する <font color="#C93E3F">**async**</font>/<font color="#C93E3F">**await**</font> の例では、以下の<font color="#C93E3F">**async**</font>関数を参照します。

```swift
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
```

複数の非同期呼び出しと戻り値を組み合わせた<font color="#C93E3F">**async**</font>/<font color="#C93E3F">**await**</font> の例。

```swift
// 複数の`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（非同期）メソッドを呼び出す
同期コードから非同期関数を呼び出すために、タスクを初期化します。

```swift
func userTappedUpload() {
    Task(priority: .default) {
        do {
            try await self.upload(
                image: UIImage(), 
                data: Data()
            )
        }
        catch {
            // Handle error
        }
    }
}
```

### b. Async Await対クロージャの比較
<font color="#C93E3F">**async**</font>と<font color="#C93E3F">**await**</font> を使用することの1つの利点は、Swiftのコードを読みやすくすることです。この記事で紹介した <font color="#C93E3F">**async**</font> 関数 `uploadTask` を補完クロージャのみを使用するように書き直した場合、この関数は読みにくい深いネストしたコールバック構造を持っています。

```swift
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による非同期プログラミング
以上です。<font color="#C93E3F">**async**</font>と<font color="#C93E3F">**await**</font> を使うことで、コールバックによる非同期タスクと非同期ロジックをSwiftで実装することができます。