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

SwiftUI MVVM模式下API调用数据传递异常问题

问题求助

发帖前我已经尝试自行排查,但毫无进展。我编写了专属的API类、ViewModel和View来实现数据交互,日志显示API调用成功且JSON解码无异常,但数据始终无法在UI视图中展示。我推测问题出在数据尚未就绪时就被访问,恳请各位帮忙解决!

现有代码

API类

import Combine
import Foundation

class CrunchbaseApi:ObservableObject
{
    @Published var companies:[Company] = [Company]()
    @Published var singleCompany:Company?

        
    func retrieve(company:String) async
    {
        let SingleEntityURL:URL = URL(string:"https://api.crunchbase.com/api/v4/entities/organizations/\(company)?card_ids=fields&user_key=**********REMOVED FOR SECURITY*****************")!
        
        let task = URLSession.shared.dataTask(with:SingleEntityURL){ data, response, error in

            let decoder = JSONDecoder()
            
            if let data = data{
                do {
                    self.singleCompany = try decoder.decode(Company.self, from: data)
                } catch  {
                    print(error.localizedDescription)
                }
            }
        }
        task.resume()
    }
    
    func retrieveCompanyList()
    {
        //declare
    }
}

ViewModel

import Combine
import Foundation

class CompanyViewModel: ObservableObject
{
    var crunchbase:CrunchbaseApi = CrunchbaseApi()
    @Published var singleCompany:Company?
    
    
    func retrieveCompany(company:String) async
    {
        await self.crunchbase.retrieve(company: company)
        self.singleCompany = crunchbase.singleCompany
        
    }
    
}

View

import SwiftUI

struct CompanyView: View
{
    @State var companyViewModel:CompanyViewModel = CompanyViewModel()
    
    
    var body: some View
    {
        
        NavigationView
        {
            VStack
            {
                Text("Company ID: \(companyViewModel.singleCompany?.id ?? "NOTHING")")
              //  Text("Company Name: \(companyViewModel.companyName)")
             //   Text("Company Summary: \(companyViewModel.companyDescription)")
             //   Text("Logo URL: \(companyViewModel.companyLogoURL)")
            }.navigationTitle("Company")
        }
    }
}

问题根源及修复方案

核心问题点

  1. 线程错误:URLSession.dataTask的回调在后台线程执行,直接更新@Published属性会导致UI无法感知状态变化(SwiftUI要求UI相关状态更新必须在主线程)。
  2. 异步逻辑失效:retrieve方法标记为async,但内部使用传统dataTask,await不会等待请求完成,导致singleCompany被提前赋值为nil。
  3. ViewModel声明错误:View中用@State持有ObservableObject实例,无法监听ViewModel的状态变化,导致UI不会刷新。
  4. 缺少API触发时机:现有View代码没有调用retrieveCompany方法,API请求根本没有执行(若日志显示调用成功,可能是在其他地方触发,但代码中未体现)。

修复后的代码

1. API类(改用async/await版网络请求)

import Combine
import Foundation

class CrunchbaseApi: ObservableObject {
    @Published var companies: [Company] = []
    @Published var singleCompany: Company?

    func retrieve(company: String) async {
        guard let url = URL(string: "https://api.crunchbase.com/api/v4/entities/organizations/\(company)?card_ids=fields&user_key=**********REMOVED FOR SECURITY*****************") else {
            print("无效URL")
            return
        }
        
        do {
            // 使用async/await版data请求,自动回到调用时的线程(主线程)
            let (data, _) = try await URLSession.shared.data(from: url)
            let decoder = JSONDecoder()
            // 若需自定义解码策略,可在此添加(比如keyDecodingStrategy)
            self.singleCompany = try decoder.decode(Company.self, from: data)
        } catch {
            print("解码失败:\(error.localizedDescription)")
        }
    }
    
    func retrieveCompanyList() {
        // 待实现
    }
}

2. ViewModel(优化状态同步)

import Combine
import Foundation

class CompanyViewModel: ObservableObject {
    private let crunchbase = CrunchbaseApi()
    @Published var singleCompany: Company?
    
    init() {
        // 自动同步CrunchbaseApi的singleCompany状态,无需手动赋值
        _ = crunchbase.$singleCompany
            .assign(to: &$singleCompany)
    }
    
    func retrieveCompany(company: String) async {
        await crunchbase.retrieve(company: company)
    }
}

3. View(修正ViewModel声明并添加触发时机)

import SwiftUI

struct CompanyView: View {
    // 使用@StateObject持有ObservableObject,确保状态变化能触发UI刷新
    @StateObject private var companyViewModel = CompanyViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Company ID: \(companyViewModel.singleCompany?.id ?? "NOTHING")")
                // 取消注释以下代码,展示更多信息
                // Text("Company Name: \(companyViewModel.singleCompany?.name ?? "N/A")")
                // Text("Company Summary: \(companyViewModel.singleCompany?.summary ?? "N/A")")
                // Text("Logo URL: \(companyViewModel.singleCompany?.logoUrl ?? "N/A")")
            }
            .navigationTitle("Company")
            .onAppear {
                // 在视图出现时触发API请求,替换为你的目标公司ID
                Task {
                    await companyViewModel.retrieveCompany(company: "your-target-company-id")
                }
            }
        }
    }
}

额外说明

  • 确保Company模型的属性与API返回的JSON字段完全匹配,否则可能出现隐性解码失败(即使日志没报错)。
  • 若需要处理加载状态(比如显示加载指示器),可以在ViewModel中添加@Published var isLoading = false,在retrieveCompany前后更新该状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 17:54:32