非同期処理をasync/awaitを利用して書くことのメリット

WWDC21で発表され、もはやどこでも発表されつくされた感のあるasync / awit。

遅ればせながら私も async/awaitをやっときちんと調べましたので、記事の形にしてみようと思います。

# 1. 非同期処理をclosure(もしくはdelegate)を利用して書く場合の注意点

例えば、下記のようなコードを書きたいとします。よくある非同期処理ですね。

```swift
func someLongLongTimeTask(completion: @escaping ((Data) -> Void), errorHandler: @escaping ((Error) -> Void)) {
    precheck()
    guard isReady else  {
        errorHandler(LongLongFuncError.setupFailure)
        return
    }
    
    someLongLongLong(completion: { data in
        guard self.validate(of: data) else {
            errorHandler(LongLongFuncError.dataValidationFailure)
            return
        }
        guard self.updated else {
            errorHandler(LongLongFuncError.selfUpdateFailure)
            return
        }
        
        completion(data)
    })
}
```
上記のコードでは、処理の完了やエラーの発生を引数として渡しているclosureで管理しようとしています。
ですが、closureは引数として渡されているため、Swiftのコンパイラ側で発火を保証することができません。
例えばこのコードは下記のように書き換えたとしても正常に動作します。

```swift
func someLongLongTimeTask(completion: @escaping ((Data) -> Void), errorHandler: @escaping ((Error) -> Void)) {
    precheck()
    guard isReady else  {
        return
    }
    
    someLongLongLong(completion: { data in
        guard self.validate(of: data) else {
            return
        }
        guard self.updated else {
            return
        }
        
        completion(data)
    })
}
```

上記コードは`errorHandler`の呼び出しをサボっただけのコードですが、この場合呼び出し元はエラーの発生を検知できません。その結果プログレスバーが表示されたままになるなどのバグが生まれる可能性があります。
またエラーの発生が極めて限定的な状況下で発生する場合、動作テストや社内テストで検知することができず、お客様トラブルへと発展する恐れがあります。

上記のテストコードはclosureを利用したコードでしたが、これをdelegateを利用して読み替えたとしても同様にdelegateの発火を強制することはできませんし、コンパイラに確認させることもできません。

このように、**closure(もしくはdelegate)を利用した非同期処理はその性質上注意深く実装し、またテストを十二分に行わないと容易にバグを生む可能性がある** コードになります
この問題を解決するのが async/awaitです。

# 2. 上記のコードをasync/awaitを利用して書き換えてみる

上記のコードをasync/awaitを利用して書き換えてみると下記のようになります。

```swift
func someLongLongTimeTask() async throws -> Data {
    precheck()
    guard isReady else { throw LongLongFuncError.setupFailure }
    
    let data = try await someLongLongLong()
    
    guard validate(of: data) else { throw LongLongFuncError.dataValidationFailure}
    guard updated else { throw LongLongFuncError.selfUpdateFailure}
    
    return data
}
```
上記のようにasync/awiatを利用してコードを書き換えると下記のようなメリットが生まれます
- closureを利用することによるコードのネスト構造はなくなり、直線的なコードになりました。
- また、guard節を利用したvalidationも明快になり、同期関数と似たような見た目になりました
- そしてさらに、Swiftのコンパイラがエラーのthrowかデータの返却を確認するようになりました。

例えば、下記のようなコードはコンパイルが通りません。これはつまり、SwiftコンパイラによってErrorのThrowを保証できているということになります。

```swift
func someLongLongTimeTask() async throws -> Data {
    precheck()
    guard isReady else { return }
    
    let data = try await someLongLongLong()
    
    guard validate(of: data) else { return }
    guard updated else { return }
    
    return data
}
```

このように、非同期処理にasync/awaitを利用することで、closureやdelegateを利用した非同期処理より、よりかんたんに、そしてより安全にコードを記述することができます。

# 3. async/awaitの注意点

async/awaitはこのように安全にコードを書くことができますが、**いつ処理が発火されるかわからないため、状態管理やスレッドには十分に注意を払う必要がある** という点には注意が必要です。
async/awaitを利用すると、それぞれの**処理がいつ行われるか**はOSによって決定されるため、実行されるThreadは多くの場合呼び出しスレッドとは違うでしょうし、もしかしたら更新中の配列に同時アクセスしてデータ競合や内部状態の破損をもたらしてしまうかもしれません。

このような問題の多くはasync/awaitと同時に発表されたactorという概念を利用することで解決できます
(後日こちらについても記事を書く予定です)

# 4. おわりに

このようにasync/awaitを利用して非同期処理を記述すると、closure /delegateを利用して書く場合に比べてよりかんたんに、そして安全にコードを書くことができます。
データの非同期処理をかんたんにするためにSingleを利用していたプロジェクトなどは、async/awaitに切り替えることで外部ライブラリの仕様を減らすことができ、良いのではないでしょうか。
(私が参画しているプロジェクトでも、十分に検討した上で随時async/awaitへの移行を進めようと考えています)