SwiftUI中API数组数据获取问题及模型正确性验证
我是SwiftUI开发者,设计了以下API Model,不确定是否正确;同时编写的ViewModel和View无法获取API中的data[]数组数据,请求帮忙排查问题。
提供的代码
1. API Model代码
public struct Temperatures { public let bookDetails: BookDetails public init(bookDetails: BookDetails) { self.bookDetails = bookDetails } } public struct BookDetails { public let data: [Datum] public init(data: [Datum]) { self.data = data } } public struct Datum : Hashable, Identifiable { public let id = UUID() public let title: String public let published: String public let url: String public init( title: String, published: String, url: String) { self.title = title self.published = published self.url = url } }
2. ViewModel代码(无法获取data数据)
class PrimeBookVM: ObservableObject{ @Published var datas = [Datum]() init(){ let source = "https://********/api/book-search?is_prime" let url = URL(string: source)! let session = URLSession(configuration: .default) session.dataTask(with: url){ (data, _, err) in if err != nil{ print(err?.localizedDescription ?? "Hello Error") return } let json = try!JSON(data: data!) for i in json["data"]{ let published = i.1["published"].stringValue let title = i.1["title"].stringValue let url = i.1["url"].stringValue DispatchQueue.main.async { self.datas.append(Datum(title: title, published: published, url: url)) } } } .resume() } }
3. View代码
struct PrimeBooksView: View{ @StateObject var list = PrimeBookVM() var body: some View{ ScrollView(.horizontal){ HStack{ ForEach(list.datas, id: \.self){ item in VStack(alignment: .leading){ WebImage(url: URL(string: item.url)!, options: .highPriority, context: nil) .resizable() .frame(width: 180, height: 230) Text(item.title) .multilineTextAlignment(.leading) .font(.system(size: 16)) .foregroundColor(Color("default")) Text(item.published) .font(.system(size: 12)) .fontWeight(.light) .foregroundColor(Color("default")) } .padding(.all,4) .background(Color.white).cornerRadius(8) .shadow(color: .gray, radius: 1) } .padding(.all,1) } } } }
问题分析与修复方案
一、Model的问题与优化
结构匹配问题:
当前Model假设API返回的JSON结构是{"bookDetails": {"data": [...]}},但如果你的API返回顶层直接是{"data": [...]},嵌套层级就完全错误,会直接导致后续解析找不到数据。请先确认API的实际返回结构。缺少Codable实现:
手动解析JSON容易出错,建议让Model实现Codable协议,用JSONDecoder解析更安全易维护。修改后的Model示例(匹配原结构):public struct Temperatures: Codable { public let bookDetails: BookDetails } public struct BookDetails: Codable { public let data: [Datum] } public struct Datum: Codable, Hashable, Identifiable { // 如果API返回专属id字段,替换成API的id,不要用本地UUID public let id = UUID() public let title: String public let published: String public let url: String }若API返回顶层就是
{"data": [...]},则简化Model:public struct BookResponse: Codable { public let data: [Datum] } public struct Datum: Codable, Hashable, Identifiable { public let id: String // 使用API返回的id字段 public let title: String public let published: String public let url: String }Identifiable的id问题:
当前Datum用本地生成的UUID()作为id,若API本身返回唯一标识(如id字段),建议直接使用API返回的id,这样Identifiable的id更准确,也避免本地UUID导致的Hashable异常。
二、ViewModel的核心问题(无法获取data数据)
JSON解析路径错误:
Model结构是Temperatures -> BookDetails -> data,但ViewModel直接取json["data"],跳过了上层的bookDetails层级,正确路径应为json["bookDetails"]["data"]。如果API返回顶层就是data数组,那Model结构错误,需先调整Model。强制解包导致的崩溃与错误忽略:
let url = URL(string: source)!:改用guard let处理无效URL:guard let url = URL(string: source) else { print("无效的URL") return }try!JSON(data: data!):改用do-catch捕获解析错误:do { let json = try JSON(data: data!) // 后续解析逻辑 } catch { print("JSON解析失败:\(error.localizedDescription)") return }
SwiftyJSON遍历错误:
遍历json["data"]得到的是键值对元组,需先转成数组再遍历:guard let dataArray = json["bookDetails"]["data"].array else { print("data数组不存在") return } var tempDatas = [Datum]() for item in dataArray { let published = item["published"].stringValue let title = item["title"].stringValue let url = item["url"].stringValue tempDatas.append(Datum(title: title, published: published, url: url)) } DispatchQueue.main.async { self.datas = tempDatas // 统一赋值减少视图刷新次数 }推荐改用Codable解析:
用JSONDecoder更简洁,修改后的ViewModel示例:class PrimeBookVM: ObservableObject{ @Published var datas = [Datum]() init(){ fetchData() } func fetchData() { let source = "https://********/api/book-search?is_prime" guard let url = URL(string: source) else { print("无效的URL") return } let session = URLSession(configuration: .default) session.dataTask(with: url){ [weak self] (data, _, err) in if let err = err { print("请求错误:\(err.localizedDescription)") return } guard let data = data else { print("无返回数据") return } do { let response = try JSONDecoder().decode(Temperatures.self, from: data) DispatchQueue.main.async { self?.datas = response.bookDetails.data } } catch { print("解析失败:\(error.localizedDescription)") print(error) // 打印完整错误信息辅助排查 } } .resume() } }
三、View的优化点
ForEach简化:
Datum已实现Identifiable,可直接写ForEach(list.datas) { item in ... },无需指定id: \.self。避免强制解包:
WebImage的URL用可选绑定处理,避免无效URL导致崩溃:if let imageUrl = URL(string: item.url) { WebImage(url: imageUrl, options: .highPriority, context: nil) .resizable() .frame(width: 180, height: 230) } else { // 显示占位图 Color.gray.frame(width: 180, height: 230) }
总结
- 先确认API实际返回的JSON结构,调整Model使其匹配;
- 修复ViewModel中的解析路径错误,替换强制解包为安全处理逻辑;
- 优先使用Codable协议解析JSON,减少手动解析的出错概率;
- 优化View中的强制解包和ForEach写法,提升代码稳定性。
内容的提问来源于stack exchange,提问作者Anup Kumar Mishra

