クロージャを使用した非同期処理をSwift Concurrency: async/awaitに書き換えてみた！

# Swift Concurrencyとは 
Swift5.5から登場した、言語レベルの並行処理の機能。
非同期処理、並行処理のコードを簡潔かつ安全に記述できる。

# async/awaitに書き換えるメリット
async/awaitを使えば、同期処理と同じような書き方で非同期処理を記述できる。
Actorを使うことでデータ競合を防ぐことができる。

# クロージャを使用した非同期処理
### クロージャーによる非同期処理の問題点
completionHandlerを呼ぶの忘れても、コンパイラが通ってしまうので、呼び忘れが発生する可能性がある！
通常、同期的なコードは上から順番に実行されるが、上、下、真ん中と読み進めなければいけません。

①：URLSession.shared.dataTaskを呼び出し、返り値であるtask変数を取得する！
②：task変数を元にresumeメソッドを呼び出し、リクエストを開始する！
③：リクエストが完了し、レスポンスが返ってきたら、completionHandler(successまたはfailure)が呼ばれる！

**呼び出し元**
```Swift
struct WeatherAPIClient {
    func getWeather(latitude: String, longitude: String, success: @escaping (String, String) -> Void, failure: @escaping () -> Void) {
        let urlString = "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=\(Constants.apiKey)"
        let url = URL(string: urlString)!
        let urlRequest = URLRequest(url: url)
        //①：URLSession.shared.dataTaskを呼び出し、返り値であるtask変数を取得する！
        let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
            guard let data else {
                failure()
                return
            }
            do {
                let decodeData = try JSONDecoder().decode(WeatherData.self, from: data)
                let description = decodeData.weather[0].main
                let cityName = decodeData.name
                //③：リクエストが完了し、レスポンスが返ってきたら、completionHandler(successまたはfailure)が呼ばれる！
                success(description, cityName)
                
            } catch {
                failure()
            }
        }
        //②：task変数を元にresumeメソッドを呼び出し、リクエストを開始する！
        task.resume()
    }
}
```
**呼び出し側**
```Swift
func showWeatherView() {
        apiClient.getWeather(
            latitude: latitude,
            longitude: longitude,
            success: { description, cityName in
                DispatchQueue.main.async {
                    self.weatherLabel.text = description
                    self.prefectureLabel.text = cityName
                }
            },
            failure: {
                DispatchQueue.main.async {
                    let alert = self.alertMaker.make(didTapRetry: {
                        self.showWeatherView()
                    })
                    self.showWeatherView()
                    self.present(alert, animated: true, completion: nil)
                }
            }
        )
    }
```
呼び出し側から`getWeather`メソッドが呼ばれ、呼び出し元のメソッドが実行され、API通信の結果に応じてイベント通知が送られる！成功したら`success`クロージャが実行され、失敗したら`failure`クロージャが実行される！

# async/awaitを使用した非同期処理
getWeatherメソッドは`async throws`のキーワードがつけられており、「エラーを投げるasync関数」である。返り値がStringなので、すべてのパスでreturnをするかエラーをスローしなければコンパイルエラーとなる。
そのため`completionHandler`を使用していた時とは異なり、正常にStringを返す時も、異常が起き、エラーを返す時も、実装忘れがないことをコンパイルが保証してくれる。
**呼び出し元**
```Swift
struct WeatherAPIClient {
    func getWeather(latitude: String, longitude: String) async throws -> (String, String) {
        let urlString = "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=\(Constants.apiKey)"
        let url = URL(string: urlString)
        let urlRequest = URLRequest(url: url!)
        
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
        switch (response as? HTTPURLResponse)?.statusCode {
        case 200:
            let decodeData = try JSONDecoder().decode(WeatherData.self, from: data)
            let description = decodeData.weather[0].main
            let cityName = decodeData.name
            return (description, cityName)
        default:
            throw APIClientError.networkError
        }
    }
}
```
## await
awaitキーワードはプログラムにそのメソッドやプロパティが待機可能であることを伝えるものです。
awaitがつけられるとプログラムは待機状態となる。
asyncが付いた関数を呼び出す際は、クロージャで完了を受け取る代わりに awaitというキーワードを付けて呼び出すことで、結果を "待つ" ことができる。
スレッドはブロックを解除し、他の作業を行うことで、並列処理が可能になる！
非同期処理が終了すると、awaitキーワードのつけられたメソッドやプロパティが評価され、結果を得ることができる。

**呼び出し側**
```Swift
func showWeatherView() {
        let (latitude, longitude) = PrefectureLatLon().fetchLatLon(weatherPrefecture: prefecture)
        Task { @MainActor in
            do {
                let (description, cityName) = try await WeatherAPIClient().getWeather(latitude: latitude, longitude: longitude)
                self.weatherLabel.text = description
                self.prefectureLabel.text = cityName
            } catch {
                let alert = AlertMaker().make(didTapRetry: {
                    self.showWeatherView()
                })
                self.present(alert, animated: true, completion: nil)
            }
        }
    }

```
## Task
async/awaitには、async関数はasync関数の中でしか呼び出せないという制約がある。
もし、async関数でない普通の関数の中でasync関数を呼び出すとコンパイルエラーになる。
async/awaitには、async関数はasync関数の中でしか呼び出せないという制約があって、`showWeatherView()`の中から呼べないという問題があった。
そんなときに役立つのが**Task**です。Taskを使うと、async関数でない普通の関数の中から、asyncな関数を呼び出すことができます！

## MainActor
MainActorはメインスレッドで実行される特別なActorです。
`MainActor`は、作業を実行するためにメインキューを使用する。`MainActor`常にメイン キューで実行され、呼び出すと選択したカスタム作業をメインアクターにプッシュする。
`DispatchQueue.main.async`の代わりにTaskを使用することで、Task内の処理がすべてメインスレッドで実行されるようになり、安全性に関しての配慮が容易となる！
# 参考文献

https://qiita.com/Ri__/items/02d18d9372136c337077

https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-5/

https://note.com/taatn0te/n/nb20962d20cf2

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/

https://tech.gunosy.io/entry/ios_async_await#影響の小さなAPIアクセスをasyncawait対応

https://www.hackingwithswift.com/quick-start/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue

https://zenn.dev/akkyie/articles/swift-concurrency

https://blog.personal-factory.com/2022/01/23/how-to-use-async-await-since-swift5_5/