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

Swift Widget中JSON API调用失败排查与修复

解决Swift Widget无法加载自定义API数据的问题

问题说明

我开发Swift Widget时,适配自定义JSON API后模拟器无法加载数据。API返回结构如下,仅需提取rates字段:

{
    "data": {
        "success": true,
        "fluctuation": true,
        "start_date": "2022-10-22",
        "end_date": "2022-11-22",
        "base": "RICE",
        "rates": {
            "USD": {
                "start_rate": 16.410000000000014,
                "end_rate": 17.920000000000137,
                "change": 1.5100000000001224,
                "change_pct": 9.201706276661309
            }
        },
        "unit": "per cwt"
    }
}

已适配结构体、修改Timeline及Timeline Provider代码,但始终无法获取数据,原教程代码可正常运行。以下是我的代码:

struct Rice: TimelineEntry, Codable {
    var date: Date = .init()
    var data: DataClass = DataClass(rates: Rates(usd: Usd(startRate: 0.0, endRate: 0.0, change: 0.0, changePct: 0.0)))
}

struct DataClass: Codable {
    var rates: Rates

    enum CodingKeys: String, CodingKey {
        case rates
    }
}

struct Rates: Codable {
    var usd: Usd

    enum CodingKeys: String, CodingKey {
        case usd = "USD"
    }
}

struct Usd: Codable {
    var startRate: Double
    var endRate: Double
    var change: Double
    var changePct: Double

    enum CodingKeys: String, CodingKey {
        case startRate = "start_rate"
        case endRate = "end_rate"
        case change
        case changePct = "change_pct"
    }
}

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> Rice {Rice(date: Date())}

    func getSnapshot(in context: Context, completion: @escaping (Rice) -> ()) {
        let entry = Rice(date: Date())
        completion(entry)
}

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
       let currentDate = Date()
        Task {
            if var riceData = try? await fetchData() {
                riceData.date = currentDate
                let nextUpdate = Calendar.current.date(byAdding: .day,value: 1, to: currentDate)!
                let timeline = Timeline(entries: [riceData], policy: .after(nextUpdate))
                completion(timeline)
            }
        }
        
    }
    func fetchData()async throws->Rice {
        let session = URLSession(configuration: .default)
        let response = try await session.data(from: URL(string: APIURL)!)
        let riceData = try JSONDecoder().decode([Rice].self, from: response.0)
        if let rice = riceData.first {
            return rice
        }
        return .init()
    }
}
fileprivate let APIURL = "https://commodities-api.com/api/fluctuation?access_key=z9075xu0tihtz55dgq56forsl1f1vgnawbvjs1qohsi7ujr8x4pax14o2b8h&base=RICE&symbols=USD&start_date=2022-10-22&end_date=2022-11-22"

问题根源及修复方案

1. 结构体与JSON结构不匹配

API返回的顶层是包含data字段的对象,但你的Rice结构体直接对应整个JSON,缺少顶层的data包裹。需新增一个顶层结构体来匹配JSON结构:

// 新增顶层结构体,对应API返回的完整结构
struct APIResponse: Codable {
    let data: DataClass
}

2. 解码错误:错误地解码为数组

你在fetchData中用JSONDecoder().decode([Rice].self, from: response.0),但API返回的是单个对象而非数组,这会直接导致解码失败。修改解码逻辑:

func fetchData() async throws -> Rice {
    let session = URLSession(configuration: .default)
    let (data, _) = try await session.data(from: URL(string: APIURL)!)
    // 解码为APIResponse,再提取data字段生成Rice对象
    let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data)
    return Rice(date: Date(), data: apiResponse.data)
}

3. 错误被吞掉,无法排查问题

try?会忽略所有错误,导致你看不到解码失败的具体原因。建议换成do-catch捕获错误,方便调试:

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    let currentDate = Date()
    Task {
        do {
            var riceData = try await fetchData()
            riceData.date = currentDate
            let nextUpdate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!
            let timeline = Timeline(entries: [riceData], policy: .after(nextUpdate))
            completion(timeline)
        } catch {
            // 打印错误信息,方便调试
            print("数据加载失败:\(error)")
            // 加载失败时返回占位数据
            let placeholderEntry = Rice(date: currentDate)
            let timeline = Timeline(entries: [placeholderEntry], policy: .after(Date().addingTimeInterval(3600)))
            completion(timeline)
        }
    }
}

4. 简化Rice结构体的默认值(可选优化)

可以简化Rice的初始化默认值,让代码更简洁:

struct Rice: TimelineEntry, Codable {
    var date: Date
    var data: DataClass
    
    // 提供默认初始化器,用于占位和快照
    init(date: Date = .init(), data: DataClass = DataClass(rates: Rates(usd: Usd(startRate: 0.0, endRate: 0.0, change: 0.0, changePct: 0.0)))) {
        self.date = date
        self.data = data
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 18:03:18