コールバック関数：API通信の結果をコールバックでViewに伝える！

# コールバック関数とは
🟦関数の呼び出し先から、🟩呼び出し元の関数を呼び出すこと！
「電話したら向こうからコールバックされるのと一緒！」という例がしっくりきました！

# コールバック関数使用例
コールバック関数を理解するために、Playgroundを使用して、理解を深めましょう！
この例ではDispatchQueue.global().asyncAfter(deadline: .now() + 5)を用いて、API通信データ取得する際をイメージしやすいようにしています！
※実施にはAPI通信を行っていない。
```swift
🟩呼び出し元の関数
func download(url: URL, success: @escaping (Data) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
//🟥5秒後に実行
        let data = Data()
        success(data)
    }
}
//①
print("start")
//🟦関数の呼び出し先
download(
    url: URL(string: "https://apple.com")!,
    success: { data in
    //③
        print("データ取得",data)
    }
)
//②
print("end")
```
プリント文の表示の順序は下記のようになります！
```
start
end
データ取得 0 bytes
```
このような順序になるのは、DispatchQueue.global().asyncAfter(deadline: .now() + 5)スコープ内の処理は🟥5秒後に実行されるためである！

### func download(url: URL, success:)の`success`が「コールバック関数」
### クロージャがスコープから抜けても存在し続けるときに、クロージャの前に `@escaping`が必要！
具体的には以下のような場合に`@escaping`が必要
・クロージャがスコープ外で強参照されるとき
・クロージャを非同期的に実行するとき

🟦関数の呼び出し先`download(）`から🟩呼び出し元の関数`func download(url:, success:)`を呼び出し、`success(data)`にて呼び出し元の引数であるメソッドを呼び、🟦関数の呼び出し先にコールバックしている！
そのため、🟩呼び出し元でデータの取得に5秒間かかっった後に、🟦関数の呼び出し先のプリント文が呼ばれています！

# API通信でのコールバック関数の使用例
## コールバック関数のありがたみ
「この処理を誰がやるのか」というのを常に考えた設計を行う際、「Viewの描画を担当する処理はViewControllerでやるべき」、「UI に関係しない内部的な計算処理だから裏側の処理を担当するModelでやるべき」というように役割を分ける際にコールバック関数のありがたみが分かりました！
#### ⭐️「ViewControllerからModelの関数を呼び出した（コール）後、Model側の関数で処理された結果を戻して（バック）もらって、その結果を元にViewの処理を実行する！」

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090887/43d81ec1-bdfe-8959-17bf-9482edb34f97.png" width=300> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090887/7d8d1fa9-e85e-de10-2372-9a9bd53d6c30.png" width=300>

上記サンプルアプリはボタンを押すと、API通信が行われ、天気と都道府県名を取得できるアプリです！
2画面でAPI通信からデータを取得する下記のgetWeatherFromAPIメソッドを呼び出すので、ViewControllerから別のModel側のファイルAPIClient.swiftに切り出すことにしました！

このメリットは、同じコードを複数箇所に書く必要がなくなる！
APIのバージョンアップの際のURLの訂正ミスが減らせる！
## コールバック関数使用前
```swift: TokushimaViewController.swift
class TokushimaViewController: UIViewController {
 
     @IBOutlet weak var weatherLabel: UILabel!
     @IBOutlet weak var prefectureLabel: UILabel!
 
     @IBAction func tappedTokushima(_ sender: UIButton) {
         getWeatherFromAPI(latitude: "35.689753", longitude: "139.691731")
     }
 
     func showAPIErrorAlert(lat: String, lon: String) {
         let alert = UIAlertController(title: "エラー", message: "通信に失敗しました。", preferredStyle: .alert)
         let action = UIAlertAction(title: "リトライ", style: .default) { (action) in
             self.getWeatherFromAPI(latitude: lat, longitude: lon)
             alert.dismiss(animated: true, completion: nil)
         }
         alert.addAction(action)
         self.present(alert, animated: true, completion: nil)
     }
 //API通信
     func getWeatherFromAPI(latitude: String, longitude: 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 task = URLSession.shared.dataTask(with: urlRequest) { [weak self]
             data, response, error in
             guard let data = data else {
                 return DispatchQueue.main.async {
                     self?.showAPIErrorAlert(lat: latitude, lon: longitude)
                 }
             }
 
             do {
                 let decodeData = try JSONDecoder().decode(WeatherData.self, from: data)
                 let description = decodeData.weather[0].main
                 let cityName = decodeData.name
                 DispatchQueue.main.async {
                     self?.weatherLabel.text = description
                     self?.prefectureLabel.text = cityName
                 }
             } catch {
                 DispatchQueue.main.async {
                     self?.showAPIErrorAlert(lat: latitude, lon: longitude)
                 }
             }
         }
         task.resume()
     }
 }
 ```
 getWeatherFromAPIメソッドをModel側のファイルに切り出す際、DispatchQueue.main.asyncはメインスレッドで行われる処理であるので、ViewController側のファイルに書きたい！けどどうすれば良いのと悩みました:cry:
 これを次に紹介するコールバック関数で解決することができます！
## コールバック関数使用後
### Model側
```swift: APIClient.swift
struct APIClient {
🟩Model側の呼び出し元の関数
    func getWeatherFromAPI(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)
        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
                success(description, cityName)

            } catch {
                failure()
            }
        }
        task.resume()
    }
}
```
### ViewController側
```swift: TokushimaViewController.swift
import UIKit

class TokushimaViewController: UIViewController {
    private let latitude = "34.065756"
    private let longitude = "134.559297"

    @IBOutlet weak var weatherLabel: UILabel!
    @IBOutlet weak var prefectureLabel: UILabel!

    @IBAction func tappedTokushima(_ sender: UIButton) {
    //🟦ViewController側の関数の呼び出し先
        APIClient().getWeatherFromAPI(
            latitude: latitude,
            longitude: longitude,
            success: { description, cityName in
                DispatchQueue.main.async {
                    self.weatherLabel.text = description
                    self.prefectureLabel.text = cityName
                }
            },
            failure: {
                DispatchQueue.main.async {
                    self.showAPIErrorAlert()
                }
            }
        )
    }
    func showAPIErrorAlert() {
        let alert = UIAlertController(title: "エラー", message: "通信に失敗しました。", preferredStyle: .alert)
        let action = UIAlertAction(title: "リトライ", style: .default) { (action) in
        //🟦ViewController側の関数の呼び出し先
            APIClient().getWeatherFromAPI(
                latitude: self.latitude,
                longitude: self.longitude,
                success: { description, cityName in
                //🟥戻ってきた結果を元に処理を実行
                    DispatchQueue.main.async {
                        self.weatherLabel.text = description
                        self.prefectureLabel.text = cityName
                    }
                },
                failure: {
                    DispatchQueue.main.async {
                        self.showAPIErrorAlert()
                    }
                }
            )
            alert.dismiss(animated: true, completion: nil)
        }
        alert.addAction(action)
        self.present(alert, animated: true, completion: nil)
    }
}
```
#### 「🟦ViewControllerからModelの関数を呼び出した（コール）後、🟩Model側の関数で処理された結果を戻して（バック）もらって、🟥その結果を元にViewの処理を実行する！」

`func getWeatherFromAPI(latitude:, longitude:, success:, failure:)`のsuccessとfailureが「コールバック関数」です！
🟦ViewController側の関数の呼び出し先`getWeatherFromAPI(）`から🟩Model側の呼び出し元の関数`func getWeatherFromAPI`を呼び出し、
API通信に成功したら、`success(data)`にてViewController側の引数であるメソッドを呼び、🟦ViewController側の関数の呼び出し先にコールバックし、ViewにAPI通信で取得したデータ天気と都道府県を表示する！
また、API通信に失敗したら、`failure()`にてViewController側の引数であるメソッドを呼び、🟦ViewController側の関数の呼び出し先にコールバックし、ViewにAlert文を表示する！

# 参考文献

https://www.youtube.com/live/Qy_N1JbSgYo?si=_jqCluTlF0OGynV9

https://qiita.com/mishimay/items/1232dbfe8208e77ed10e

https://qiita.com/maztak/items/ac668f5d8a47a46b8cb5

https://developer.apple.com/documentation/dispatch/dispatchqueue/

https://developer.apple.com/documentation/dispatch/dispatchqueue/2300020-asyncafter