【XCode15】APIの非同期処理で得たデータをTableViewに表示させる方法

# はじめに
研修としてAPIを使った天気予報アプリの作成を行った際、APIの非同期処理で得たデータをTableVeiwに表示させる方法がわからず苦戦したため、理解したことを記載する。

# APIの非同期通信
### APIとデータ構造について
天気予報のAPIは[Open-Meteo](https://open-meteo.com/)を使用。
今回使うURLは以下の通りである。
https://api.open-meteo.com/v1/forecast?latitude=35.6894&longitude=139.6917&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max

APIで受け取ったJSONデータのデコードのために、データ構造を作成しておく。
```swift:SearchResponse.swift
struct SearchResponse: Decodable {
    let daily: WeatherData
}
```
```swift:WeatherData.swift
struct WeatherData: Decodable {
    let date: [String]
    let weatherCode: [Int]
    let tempMax: [Float]
    let tempMin: [Float]
    let rainPer: [Int]
    
    enum CodingKeys: String, CodingKey {
        case date = "time"
        case weatherCode = "weather_code"
        case tempMax = "temperature_2m_max"
        case tempMin = "temperature_2m_min"
        case rainPer = "precipitation_probability_max"
    }
}
```
SearchResponseには受け取ったデータのkeyがdailyのデータのみが入り、その構造がWeatherDataとなる。

### 非同期通信について
非同期通信にはクロージャを用いる。

```swift
// APIから天気予報を取得
private func fetchArticle(completion: @escaping (WeatherData) -> Void) {

    let url = "https://api.open-meteo.com/v1/forecast"
    guard var urlComponents = URLComponents(string: url) else { return }
    let queryItems = ["latitude": "35.6894",
                      "longitude": "139.6917",
                      "daily": "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max"].map {
        URLQueryItem(name: $0.key, value: $0.value)
    }
    urlComponents.queryItems = queryItems

    URLSession.shared.dataTask(with: urlComponents.url!, completionHandler: { data, response, error in
        do {
            guard let data = data else { return }
            let searchResponse = try JSONDecoder().decode(SearchResponse.self, from: data)
            DispatchQueue.main.async() {
                completion(searchResponse.daily)
            }
        } catch {
            print(error.localizedDescription)
        }
    }).resume()
}
```
```swift
completion(searchResponse.daily)
```
により、WeatherData型のデータが変数として返る関数の様な動きをする。

この関数をViewControllerで呼び出すと以下の通りである。
```swift:ViewController.swift
class ViewController: UIViewController {
    
    private var weatherData: WeatherData?
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        fetchArticle(){ fetchWeatherData in
            
            // fetchWeatherDataに受け取ったデータが入っているので、ここに必要な処理を記載する
            // 例：クラス内に定義したweatherData変数に格納する
            self.weatherData = fetchWeatherData
            
        }
    }
}
```
こうすることで、非同期処理が実行された後に取得したデータを変数に格納できる。


# TableVeiwで表示させる
上記で取得したデータ構造はリストでないため、TableViewに表示させるためにはデータ形状を修正する必要があるが、今回は主旨ではないため省略する。
形状を修正した後の構造は、以下の通りである。
```swift:Weathers.swift
struct Weathers {
    let date: String
    let weather: String
    let tempMax: String
    let tempMin: String
    let rainPer: String
}
```
ViewControllerクラスで、Weathers型のリストとして定義する。
```swift:ViewController.swift
class ViewController: UIViewController {
    
    private var weathers = [Weathers]()
    
    ...
}
```
定義したweathersをデータとして使うように、TableViewのcellの設定を行うと以下のようになる。
```swift
extension ViewController:UITableViewDataSource, UITableViewDelegate {
    // cell数の設定
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return weathers.count
    }
    // cellの中身
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
        let weather = weathers[indexPath.row]
        
        cell.dateLabel.text    = weather.date
        cell.weatherLabel.text = weather.weather
        cell.rainPerLabel.text = weather.rainPer
        cell.tempMaxLabel.text = weather.tempMax
        cell.tempMinLabel.text = weather.tempMin
        
        return cell
    }
}
```

しかし、このままでは表示できない。
これにはUIViewControllerのライフサイクルが影響している。
ライフサイクルとは、簡単にいうと、処理の実行順のことである。
今回の場合は、非同期処理で天気予報データを取得し終わる前に、TableViewの表示処理が終わってしまうため、
データがない状態でTableViewの表示処理が行われている。

それを解決するために、変数の定義を以下の通りに変更する。
```swift
private var weathers = [Weathers](){
    didSet {
        tableView.reloadData() // tableViewを更新
    }
}
```
変数にdidSetを定義することで、変数の値が変更された後に何かしらの処理を行うことができる。
そこにtableView.reloadData()を記載することで、TableViewの表示の更新を行うことができる。
こうすることで、データ取得の非同期処理終了後にTableViewの表示処理を再度実行させることができ、TableViewにデータが表示される。

# まとめ
今回はクロージャを用いた非同期処理でAPIのデータを受け取る方法と、受け取った後のデータをTableVeiwに表示させる方法についてまとめた。
### ポイント
* クロージャを用いた非同期処理で、処理が終わった後にデータを変数に格納すること
* TableViewへデータを渡す際は、ライフサイクルに気をつけること