You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击按钮更新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"
        }
    }
}

错误原因

  1. API错误响应未处理:当请求的股票代码无效、API密钥超限或网络异常时,Alpha Vantage不会返回预期的Meta Data结构,而是返回包含Note或Error Message的错误JSON,导致解码时找不到目标键。
  2. 异步逻辑顺序错误:updateAPI()中调用getStockData后立刻遍历allStocks,但网络请求是异步的,此时allStocks还未被填充,既无法更新数据,还可能引发后续逻辑异常。
  3. 强制解包风险:latestClose中使用timeSeriesDaily[mostRecentDate]!强制解包,若API返回的Time Series (Daily)为空,会直接触发崩溃。
  4. 环境变量使用错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:10:33