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

SwiftUI多股票代码JSON解码异常问题求助

SwiftUI中批量请求股票JSON数据时结果随机、类型不匹配问题的解决方案

一、核心问题:单个AnyCancellable导致订阅被覆盖

你用单个AnyCancellable实例存储订阅,循环请求多个股票代码时,每次新请求的订阅都会覆盖之前的,导致未完成的请求被强制取消,这就是结果随机(有时显示部分、有时全显/全不显)的根本原因。

修复方案:改用Set<AnyCancellable>存储所有订阅,避免覆盖:

class ShareData: ObservableObject {
    @Published var stocks: [Stock] = []
    // 用集合存储所有订阅,每个请求的订阅都会被保留
    private var subscriptions = Set<AnyCancellable>()
    
    func fetchSingleStock(for symbol: String) {
        guard let url = URL(string: "你的接口地址?symbol=\(symbol)") else { return }
        
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: Stock.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { completion in
                    if case .failure(let error) = completion {
                        print("请求\(symbol)失败:\(error.localizedDescription)")
                    }
                },
                receiveValue: { [weak self] stock in
                    self?.stocks.append(stock)
                }
            )
            .store(in: &subscriptions) // 每个订阅存入集合,不会被覆盖
    }
    
    // 批量触发所有股票请求
    func fetchAllStocks(_ symbols: [String]) {
        stocks.removeAll() // 清空旧数据避免重复
        symbols.forEach { fetchSingleStock(for: $0) }
    }
}

二、修复getStockData的返回类型不匹配问题

你遇到的返回类型/解码错误,本质是JSON结构和Stock结构体不匹配,分两种场景处理:

场景1:接口返回单个股票对象(单代码请求)

如果接口返回的JSON是单个Stock结构:

{
  "symbol": "AAPL",
  "price": 150.25,
  "company_name": "Apple Inc."
}

对应的Stock结构体要和字段严格匹配,字段名不一致时需加CodingKeys:

struct Stock: Codable, Identifiable {
    let id = UUID() // SwiftUI列表所需的唯一标识
    let symbol: String
    let price: Double
    let companyName: String
    
    enum CodingKeys: String, CodingKey {
        case symbol, price
        case companyName = "company_name" // 映射JSON的蛇形字段
    }
}

此时getStockData(用async/await实现)的正确写法:

func getStockData(for symbol: String) async throws -> Stock {
    guard let url = URL(string: "你的接口地址?symbol=\(symbol)") else {
        throw NSError(domain: "InvalidURL", code: -1)
    }
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(Stock.self, from: data)
}

场景2:接口返回多个股票数组(批量代码请求)

如果接口支持一次传入多个代码,返回JSON数组:

[
  {"symbol": "AAPL", "price": 150.25},
  {"symbol": "MSFT", "price": 300.50}
]

getStockData应返回[Stock],结构体保持不变:

func getStockData(for symbols: [String]) async throws -> [Stock] {
    let symbolStr = symbols.joined(separator: ",")
    guard let url = URL(string: "你的接口地址?symbols=\(symbolStr)") else {
        throw NSError(domain: "InvalidURL", code: -1)
    }
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode([Stock].self, from: data)
}

三、SwiftUI视图中正确展示数据

用@StateObject绑定数据模型,遍历数组展示:

struct StockListView: View {
    @StateObject private var shareData = ShareData()
    let targetSymbols = ["AAPL", "MSFT", "GOOG"]
    
    var body: some View {
        List(shareData.stocks) { stock in
            VStack(alignment: .leading) {
                Text(stock.symbol)
                    .font(.headline)
                Text("$\(stock.price, specifier: "%.2f")")
                    .font(.subheadline)
                Text(stock.companyName)
                    .font(.caption)
            }
        }
        .onAppear {
            shareData.fetchAllStocks(targetSymbols)
        }
    }
}

四、额外排查点

  • 检查接口是否有请求频率限制,循环请求过快可能被限流,导致部分请求失败
  • 若JSON字段是蛇形命名,需配置解码器:
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    
  • 在sink的failure分支打印完整错误,能快速定位解码失败的具体原因(比如字段缺失、类型不匹配)

内容的提问来源于stack exchange,提问作者imjonu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:22:27