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

SwiftUI中API数组数据获取问题及模型正确性验证

SwiftUI API数据获取问题排查及Model正确性验证

我是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的问题与优化

  1. 结构匹配问题:
    当前Model假设API返回的JSON结构是{"bookDetails": {"data": [...]}},但如果你的API返回顶层直接是{"data": [...]},嵌套层级就完全错误,会直接导致后续解析找不到数据。请先确认API的实际返回结构。

  2. 缺少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
    }
    
  3. Identifiable的id问题:
    当前Datum用本地生成的UUID()作为id,若API本身返回唯一标识(如id字段),建议直接使用API返回的id,这样Identifiable的id更准确,也避免本地UUID导致的Hashable异常。

二、ViewModel的核心问题(无法获取data数据)

  1. JSON解析路径错误:
    Model结构是Temperatures -> BookDetails -> data,但ViewModel直接取json["data"],跳过了上层的bookDetails层级,正确路径应为json["bookDetails"]["data"]。如果API返回顶层就是data数组,那Model结构错误,需先调整Model。

  2. 强制解包导致的崩溃与错误忽略:

    • 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
      }
      
  3. 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 // 统一赋值减少视图刷新次数
    }
    
  4. 推荐改用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的优化点

  1. ForEach简化:
    Datum已实现Identifiable,可直接写ForEach(list.datas) { item in ... },无需指定id: \.self。

  2. 避免强制解包:
    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)
    }
    

总结

  1. 先确认API实际返回的JSON结构,调整Model使其匹配;
  2. 修复ViewModel中的解析路径错误,替换强制解包为安全处理逻辑;
  3. 优先使用Codable协议解析JSON,减少手动解析的出错概率;
  4. 优化View中的强制解包和ForEach写法,提升代码稳定性。

内容的提问来源于stack exchange,提问作者Anup Kumar Mishra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 12:10:34