APIを叩いて非同期処理で株価を取得する
本記事の目的は、SwiftUI初心者がAlpha Vantage Inc.のAPIを使用して、米株市場の銘柄を取得できるようになることです。株価のチャートを表示するのは別の記事で投稿しようと思います。
APIKeyの取得 ~ APIの詳細について
APIKeyの取得手順
1.Alpha Vantage Inc.のサイトから「GET YOUR FREE API KEY TODAY」をクリックする。
2.適当な情報を入力する。
3.正常に処理が実行されるとAPIKeyが表示された画面に遷移するため、APIKeyをどこかにコピーする。
APIの詳細について
どのような状態のものが取得できるのか、想像していたよりも詳細に記載されていて驚きました。言語固有のガイドなどあったのも(少ないですが)良かったと思います。
今回は、「TIME_SERIES_DAILY」を使用しました。
Json形式で5ヶ月前までの、毎日の価格(始値、高値、安値、終値)と出来高を取得できるというものです。
実際の処理について
APIの取得で使用するファイルの構成は以下のようになっています。
HomeView.swift:ユーザーが操作するためのUIを定義したファイル
JsonData.swift:Jsonをデコードするための構造体を定義したファイル
AlphavantageViewModel.swift:非同期処理でAPI取得するため処理を定義したファイル
HomeView.swiftについて
今回の記事では、チャートは表示しないため、HomeView.swiftは一部省略した形となっています。次の記事で取得した株価をチャートにして表示させます。
変数で定義したapikeyは、取得したAPIKeyを直書きして下さい。本来コードに設定値などをハードコーディングするのはよろしくないです。別ファイル(xmlファイルなど)で定義することによって管理することをお勧めします。
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を使用しましょう!!
jsonから特定の言語のモデルを作成してくれるツールです。
対応している言語は、C#、C++、java、javascript、typescript、python、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について
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を取得して、デコードする処理はこちらを参考にしました。
まず、@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は初心者であるため、まだまだ理解できてない部分が沢山ありますが、分かりやすいように更新していきたいです。
株価をチャートにした状態が以下のようなイメージです。