尝试通过单击按钮更新API时出错



我正在尝试通过单击按钮来更新API值。这是更新功能:

func updateAPI() {
withAnimation {
model.allStocks = []
for stock in depot.aktienKatArray {
model.getStockData(for: stock.aKat_symbol ?? "")
for allS in model.allStocks {
if allS.metaData.symbol == stock.aKat_symbol {
stock.aKat_currPerShare = Double(allS.latestClose) ?? 0
}
}
}
PersistenceController.shared.saveContext()
}
}

我用两个。。在循环中,将api值(latestClose(分配给AktieKat实体中的相应股票。该视图顶部有一个更新按钮和一个属性为aKat_currPerShare的股票列表,每次我更新API(单击按钮(时,aKat_cirrPerShare都应该获得API更新的latestClose值。

点击按钮时,Xcode中输出的错误消息出现:

keyNotFound(编码键(字符串值:"元数据",intValue:nil(,Swift。解码错误。上下文(codingPath:[],debugDescription:"没有与键CodingKeys关联的值(字符串值:"元数据",intValue:nil(("元数据"(&";,underlyingError:nil((

这是我的API模型:

final class APIModel: ObservableObject {
@Environment(.managedObjectContext) private var viewContext
@Published var allStocks: [StockData] = []
private var cancellables = Set<AnyCancellable>()
@Published var stockEntities: [AktieKat] = []
init() {
loadAllStocks()
}
func loadAllStocks() {
allStocks = []
stockEntities.forEach { stockEntity in
getStockData(for: stockEntity.aKat_symbol ?? "")
}
}
func getStockData(for symbol: String) {
let url = URL(string: "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=(symbol)&outputsize=full&apikey=(APIKEY ?? "E11H9EDJNUB2N1UJ")")!
URLSession.shared.dataTaskPublisher(for: url)
.tryMap { element -> Data in
guard let response = element.response as? HTTPURLResponse,
response.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return element.data
}
.decode(type: StockData.self, decoder: JSONDecoder())
.sink { completion in
switch completion {
case .failure(let error):
print(error)
return
case .finished:
return
}
} receiveValue: { [unowned self] returnedStocks in
DispatchQueue.main.async {
self.allStocks.append(returnedStocks)
}
}
.store(in: &cancellables)

}

}

这些是编码键:

struct StockData: Codable {
var metaData: MetaData
var timeSeriesDaily: [String: TimeSeriesDaily]
//    var latestClose: String {
//        timeSeriesDaily.first?.value.close ?? ""
//    }
var latestClose: String {
guard let mostRecentDate = timeSeriesDaily.keys.sorted(by: >).first else { return "" }
return timeSeriesDaily[mostRecentDate]!.close
}
private enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case timeSeriesDaily = "Time Series (Daily)"
}
struct MetaData: Codable {
let information: String
let symbol: String
let lastRefreshed: String
let outputSize: String
let timeZone: String

private enum CodingKeys: String, CodingKey {
case information = "1. Information"
case symbol = "2. Symbol"
case lastRefreshed = "3. Last Refreshed"
case outputSize = "4. Output Size"
case timeZone = "5. Time Zone"
}
}
struct TimeSeriesDaily: Codable {
var open: String
var high: String
var low: String
var close: String
var volume: String

private enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
}
}
{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "DAI.DEX",
"3. Last Refreshed": "2022-04-05",
"4. Output Size": "Full size",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2022-04-05": {
"1. open": "64.4900",
"2. high": "64.8200",
"3. low": "62.6200",
"4. close": "62.9600",
"5. volume": "3425810"
},
"2022-04-04": {
"1. open": "63.9900",
"2. high": "64.5400",
"3. low": "62.8100",
"4. close": "64.2600",
"5. volume": "2538008"
}
}

下面是我使用Swift async/await并发框架获取股票的测试代码(只是为了好玩(。

我知道你说I'm not that experienced in swift一开始理解起来会很沉重。但学习如何使用它以后会有回报,尤其是在这种情况下,你想通过大量的网络通话下载大量信息。希望有帮助,如果不告诉我,我会删除我的答案。

另请参阅:

https://developer.apple.com/documentation/swift/updating_an_app_to_use_swift_concurrency

https://developer.apple.com/news/?id=2o3euotz

import Foundation
import SwiftUI
struct StockData: Identifiable, Codable {
let id = UUID()  // <-- here
var metaData: MetaData?
var timeSeriesDaily: [String: TimeSeriesDaily]?

var latestClose: String {
if let timeseries = timeSeriesDaily {
guard let mostRecentDate = timeseries.keys.sorted(by: >).first else { return "" }
return timeseries[mostRecentDate]!.close
} else {
return ""
}
}

private enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case timeSeriesDaily = "Time Series (Daily)"
}
}
struct MetaData: Codable {
let information: String
let symbol: String
let lastRefreshed: String
let outputSize: String
let timeZone: String

private enum CodingKeys: String, CodingKey {
case information = "1. Information"
case symbol = "2. Symbol"
case lastRefreshed = "3. Last Refreshed"
case outputSize = "4. Output Size"
case timeZone = "5. Time Zone"
}
}
struct TimeSeriesDaily: Codable {
var open: String
var high: String
var low: String
var close: String
var volume: String

private enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
}
// for testing
struct AktieKat: Identifiable, Codable {
let id = UUID()  // <-- here
var aKat_symbol: String?
var aKat_currPerShare: Double?

private enum CodingKeys: String, CodingKey {
case aKat_symbol, aKat_currPerShare
}
}
final class APIModel: ObservableObject {
@Environment(.managedObjectContext) private var viewContext

let apikey = "E11H9EDJNUB2N1UJ"

@Published var allStocks: [StockData] = []
@Published var stockEntities: [AktieKat] = []

init() {
// for testing
self.stockEntities = [AktieKat(aKat_symbol: "AAP"), AktieKat(aKat_symbol: "MSFT")]
Task {
await loadAllStocks()
}
}

func loadAllStocks() async {
Task{@MainActor in
allStocks = await loadAllStocks(for: stockEntities)
}
}

// fetch all StockData concurrently
func loadAllStocks(for arr: [AktieKat]) async -> [StockData] {
var results: [StockData] = []
// get all stocks concurrently
await withTaskGroup(of: (StockData?).self) { group -> Void in
arr.forEach { stockEntity in
group.addTask {
await (self.getStockData(for: stockEntity.aKat_symbol))
}
}
for await value in group {
if let stock = value {
results.append(stock)
}
}
}
return results
}

// fetch one StockData for one symbol
func getStockData(for theSymbol: String?) async -> StockData? {
if let symbol = theSymbol,
let url = URL(string: "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=(symbol)&outputsize=full&apikey=(apikey)") {
do {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(StockData.self, from: data)
}
catch { print(error) }
}
return nil
}

}
struct ContentView: View {
@StateObject var apiModel = APIModel()

// for testing
@State var depot_aktienKatArray = [AktieKat(aKat_symbol: "IBM")]

var body: some View {
VStack (spacing: 33){
Button("update") {
updateAPI()
}.buttonStyle(.bordered).padding(20)

if apiModel.allStocks.count == 0 {
ProgressView()
} else {
Text("(apiModel.allStocks.count) stocks fetched")
}

List(apiModel.allStocks) { stock in
HStack (spacing: 20) {
Text(stock.metaData?.symbol ?? "no data")
Text(stock.latestClose).foregroundColor(.red)
}
}
}
}

// not sure what you want to do here
func updateAPI() {
withAnimation {
Task {@MainActor in
apiModel.allStocks = []
apiModel.allStocks = await apiModel.loadAllStocks(for: depot_aktienKatArray)
for stock in apiModel.allStocks {
if let index = depot_aktienKatArray.firstIndex(where: {$0.aKat_symbol == stock.metaData.symbol}) {
depot_aktienKatArray[index].aKat_currPerShare = Double(stock.latestClose) ?? 0
}
}
//                PersistenceController.shared.saveContext()
}
}
}

}

最新更新