点击按钮更新API时触发keyNotFound解码错误求助
解码错误排查:keyNotFound(CodingKeys(stringValue: "Meta Data"))
我通过点击按钮调用updateAPI()更新API数据,意图将返回的latestClose值分配给Core Data实体AktieKat的aKat_currPerShare属性,但点击按钮时触发了解码错误。
错误信息
keyNotFound(CodingKeys(stringValue: "Meta Data", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "Meta Data", intValue: nil) ("Meta Data").", underlyingError: nil))
相关代码片段
1. Update函数代码
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() } }
2. APIModel代码
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) }
3. StockData及相关Codable结构
struct StockData: Codable { var metaData: MetaData var timeSeriesDaily: [String: TimeSeriesDaily] 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" } } }
API返回的正确JSON示例
{ "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" } } }
错误原因
- API错误响应未处理:当请求的股票代码无效、API密钥超限或网络异常时,Alpha Vantage不会返回预期的
Meta Data结构,而是返回包含Note或Error Message的错误JSON,导致解码时找不到目标键。 - 异步逻辑顺序错误:
updateAPI()中调用getStockData后立刻遍历allStocks,但网络请求是异步的,此时allStocks还未被填充,既无法更新数据,还可能引发后续逻辑异常。 - 强制解包风险:
latestClose中使用timeSeriesDaily[mostRecentDate]!强制解包,若API返回的Time Series (Daily)为空,会直接触发崩溃。 - 环境变量使用错误:
APIModel作为ObservableObject,无法直接通过@Environment(\.managedObjectContext)获取Core Data上下文,该配置无效。
解决方法
1. 处理API错误响应
修改getStockData,先校验返回的JSON是否为错误响应,再进行解码:
func getStockData(for symbol: String) { guard let url = URL(string: "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=\(symbol)&outputsize=full&apikey=\(APIKEY ?? "E11H9EDJNUB2N1UJ")") else { print("无效的URL") return } URLSession.shared.dataTaskPublisher(for: url) .tryMap { element -> Data in guard let response = element.response as? HTTPURLResponse, response.statusCode == 200 else { throw URLError(.badServerResponse) } // 检查是否为错误响应 let json = try JSONSerialization.jsonObject(with: element.data) as? [String: Any] if let note = json?["Note"] as? String { throw NSError(domain: "AlphaVantage", code: 0, userInfo: [NSLocalizedDescriptionKey: note]) } if let errorMsg = json?["Error Message"] as? String { throw NSError(domain: "AlphaVantage", code: 1, userInfo: [NSLocalizedDescriptionKey: errorMsg]) } return element.data } .decode(type: StockData.self, decoder: JSONDecoder()) .sink { completion in switch completion { case .failure(let error): print("获取股票数据失败:\(error.localizedDescription)") case .finished: break } } receiveValue: { [unowned self] returnedStocks in DispatchQueue.main.async { self.allStocks.append(returnedStocks) // 请求完成后直接更新对应实体 if let stockEntity = self.stockEntities.first(where: { $0.aKat_symbol == returnedStocks.metaData.symbol }) { stockEntity.aKat_currPerShare = Double(returnedStocks.latestClose) ?? 0 PersistenceController.shared.saveContext() } } } .store(in: &cancellables) }
2. 修复异步逻辑
删除updateAPI()中无效的嵌套循环,改为触发批量请求:
func updateAPI() { withAnimation { model.allStocks = [] model.cancellables.removeAll() // 清空旧订阅,避免重复请求 model.stockEntities = depot.aktienKatArray model.loadAllStocks() } }
3. 消除强制解包风险
修改latestClose实现,避免强制解包:
var latestClose: String { guard let mostRecentDate = timeSeriesDaily.keys.sorted(by: >).first, let dailyData = timeSeriesDaily[mostRecentDate] else { return "0.0" } return dailyData.close }
4. 修复Core Data上下文传递
修改APIModel初始化方式,手动传入上下文:
final class APIModel: ObservableObject { private let viewContext: NSManagedObjectContext @Published var allStocks: [StockData] = [] private var cancellables = Set<AnyCancellable>() @Published var stockEntities: [AktieKat] = [] init(viewContext: NSManagedObjectContext) { self.viewContext = viewContext loadAllStocks() } // 其他代码保持不变 }
在视图中初始化时传入上下文:
@Environment(\.managedObjectContext) private var viewContext @StateObject private var model: APIModel init() { _model = StateObject(wrappedValue: APIModel(viewContext: viewContext)) }
内容的提问来源于stack exchange,提问作者SPR
相关产品推荐
相关产品推荐

