SwiftUIで株価をチャートで表示する 「API取得編」

## **APIを叩いて非同期処理で株価を取得する**
本記事の目的は、SwiftUI初心者が[**Alpha Vantage Inc.**](https://www.alphavantage.co/)のAPIを使用して、米株市場の銘柄を取得できるようになることです。株価のチャートを表示するのは別の記事で投稿しようと思います。
![5株価イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128814/5dcdf0ef-4259-c6df-ca29-0a54a46b12ac.png)

## **APIKeyの取得 ~ APIの詳細について**
**APIKeyの取得手順**
1.[**Alpha Vantage Inc.**](https://www.alphavantage.co/)のサイトから「GET YOUR FREE API KEY TODAY」をクリックする。
![1(APIKey取得).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128814/978980ed-b835-c052-bf34-a4008ea5e5f0.png)
2.適当な情報を入力する。
![2(APIKey取得).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128814/7bbf9a2a-cd3b-f61a-c444-9a990d0d9abe.png)

3.正常に処理が実行されるとAPIKeyが表示された画面に遷移するため、**APIKeyをどこかにコピー**する。

**[APIの詳細](https://www.alphavantage.co/documentation/)について**
どのような状態のものが取得できるのか、想像していたよりも詳細に記載されていて驚きました。言語固有のガイドなどあったのも（少ないですが）良かったと思います。
今回は、「TIME_SERIES_DAILY」を使用しました。
Json形式で５ヶ月前までの、毎日の価格（始値、高値、安値、終値）と出来高を取得できるというものです。

## **実際の処理について**
APIの取得で使用するファイルの構成は以下のようになっています。

>HomeView.swift：ユーザーが操作するためのUIを定義したファイル
JsonData.swift：Jsonをデコードするための構造体を定義したファイル
AlphavantageViewModel.swift：非同期処理でAPI取得するため処理を定義したファイル

**HomeView.swiftについて**
今回の記事では、チャートは表示しないため、HomeView.swiftは一部省略した形となっています。次の記事で取得した株価をチャートにして表示させます。
変数で定義したapikeyは、取得したAPIKeyを直書きして下さい。本来コードに設定値などをハードコーディングするのはよろしくないです。別ファイル(xmlファイルなど)で定義することによって管理することをお勧めします。

```swift:HomeView.swift
import SwiftUI
import SwiftUICharts

struct HomeView: View {

    //テキストエディターの編集フラグ
    @State private var editting = false
    //テキストエディターで入力された文字列
    @State var text: String = ""
    //使用するAPIKey
    @State var apikey: String = "yourapikey"
    //APIでJson形式をデコードした株価の情報が格納される
    @State var stock: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
    //非同期で更新される変数を扱えるようにする
    @ObservedObject var viewModel: AlphavantageViewModel = AlphavantageViewModel()

    var body: some View {
        
        VStack {
            ZStack(alignment: .leading) {
                TextField("", text: $text, onEditingChanged: { begin in
                    // 入力開始処理
                    if begin {
                        self.editting = true
                        text = ""
                        stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
                    // 入力終了処理
                    } else {
                        self.editting = false
                    }
                //テキストフィールドに入力されたら、イベント発火するようにする
                },onCommit:{
                    viewModel.request(company: text, apikey: apikey, completion: {
                        stock = viewModel.stockData
                    })
                })
            }
        }
    }
}
```
**JsonData.swiftについて**
 APIで取得してきたJson形式のデータをデコードすることによって、swiftのオブジェクトとして扱えるようにします。
Json形式の構造体の定義ですが、初心者には取得するJsonの形式によっては複雑で、どのように定義したら良いか分からない状態になると思います。

初心者に限らず、時間がない！、全然頭が働かない時！ありますよね。
そんな時には、[quicktype](https://app.quicktype.io)を使用しましょう！！
jsonから特定の言語のモデルを作成してくれるツールです。
対応している言語は、C#、C++、java、javascript、typescript、python、swiftなどです。
以下のようなイメージです。
![3(json定義イメージ).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128814/b60b24a2-b1b4-58e1-8817-c528dc75bb71.png)
完全に信用するべきではないですが、参考にする程度でも良いかなと思います。

```swift:JsonData.swift
import Foundation
public struct Stock: Codable {
    let metaData: MetaData
    let timeSeriesDaily: [String: TimeSeriesDaily]

    enum CodingKeys: String, CodingKey {
        case metaData = "Meta Data"
        case timeSeriesDaily = "Time Series (Daily)"
    }
}

struct MetaData: Codable {
    let the1Information, the2Symbol, the3LastRefreshed, the4OutputSize: String
    let the5TimeZone: String

    enum CodingKeys: String, CodingKey {
        case the1Information = "1. Information"
        case the2Symbol = "2. Symbol"
        case the3LastRefreshed = "3. Last Refreshed"
        case the4OutputSize = "4. Output Size"
        case the5TimeZone = "5. Time Zone"
    }
}

struct TimeSeriesDaily: Codable {
    let the1Open, the2High, the3Low, the4Close: String
    let the5Volume: String

    enum CodingKeys: String, CodingKey {
        case the1Open = "1. open"
        case the2High = "2. high"
        case the3Low = "3. low"
        case the4Close = "4. close"
        case the5Volume = "5. volume"
    }
}
```
**AlphavantageViewModel.swiftについて**

```swift:AlphavantageViewModel.swift
import Foundation
import SwiftUI
 
class AlphavantageViewModel: ObservableObject{

    @Published var stockData: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
    
    func request(company: String, apikey: String, completion: @escaping () -> Void) {
        
        //アクセスするURLを作成する
        let urlString = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=\(company)&apikey=\(apikey)"
        let url = URL(string: urlString)
        let req = URLRequest(url: url!)
        let task = URLSession.shared.dataTask(with: req) { (data, response, error) in
            guard let data = data else { return }
            do{
                // デコード処理
                let requestResults: Stock = try JSONDecoder().decode(Stock.self, from: data)
                // UIを変更する
                DispatchQueue.main.async {
                    self.stockData = requestResults
                    completion()
                }
                
            } catch let error {
                print(error)
            }
        }
        task.resume()
    }
}
```

## **コードベースの解説**
HomeView.swiftについて重要な部分は、テキストフィールドで入力し終えた時に、onCommitの処理が行われることです。
onCommit内の処理で、AlphavantageViewModel.swiftで定義したrequestメソッドが呼び出されています。そこで、引数に変数（入力された銘柄、APIKey）が渡されています。
また、クロージャ(completion)を定義して、取得してきた株の情報をstockに格納しています。
あとはお好みで、stockをViewで反映させるだけです。

```
},onCommit:{
    viewModel.request(company: text, apikey: apikey, completion: {
        stock = viewModel.stockData
    })
})
```

JsonData.swiftについて重要な部分は、enumで囲った部分です。
Json形式でデコードするためには、取得してきた値と定義した変数を同じ命名にする必要があります。しかし、変数の定義にスペースや()は含めることができません。
CodingKeyを使用することによって、定義した変数がJsonで取得している値と同じ状態にしています。keyとValueのイメージ。

```
public struct Stock: Codable {
    let metaData: MetaData
    let timeSeriesDaily: [String: TimeSeriesDaily]

    enum CodingKeys: String, CodingKey {
        case metaData = "Meta Data"
        case timeSeriesDaily = "Time Series (Daily)"
    }
}
```

AlphavantageViewModel.swiftについて重要な部分はいくつかあります。
Jsonを取得して、デコードする処理は[こちら](https://ymgsapo.com/2020/06/28/swift-json-decode/)を参考にしました。
まず、＠Published属性を付与した変数があることです。
＠PublishedはObservableObjectプロトコルに準拠したクラス内のプロパティを監視し、変化があった際にViewに対して通知（反映する）ことができます。

```
@Published var stockData: Stock = Stock(metaData: MetaData(the1Information: "", the2Symbol: "", the3LastRefreshed: "", the4OutputSize: "", the5TimeZone: ""), timeSeriesDaily: [:])
```

そして、監視したい変数（stockDataをViewで反映させるため）は、View側(HomeView.swift)で＠ObservedObjectを使用して、以下のように定義する必要があります。

```
//非同期で更新される変数を扱えるようにする
@ObservedObject var viewModel: AlphavantageViewModel = AlphavantageViewModel()
```

クロージャがスコープを抜けても存在し続ける時に、＠escaping属性をクロージャ(completion)に付与している。

```
func request(company: String, apikey: String, completion: @escaping () -> Void) {
```

DispatchQueue.main.asyncでUIを更新する処理を非同期処理で行なっています。

```
// UIを変更する
DispatchQueue.main.async {
    self.stockData = requestResults
    completion()
}
```

## **最後に**
Swiftは初心者であるため、まだまだ理解できてない部分が沢山ありますが、分かりやすいように更新していきたいです。
株価をチャートにした状態が以下のようなイメージです。
![4(完成イメージ).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128814/a805655f-711b-d2e2-561c-431fe7fb0f68.png)