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
相关产品推荐
相关产品推荐

