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") } } }
问题根源及修复方案
核心问题点
- 线程错误:
URLSession.dataTask的回调在后台线程执行,直接更新@Published属性会导致UI无法感知状态变化(SwiftUI要求UI相关状态更新必须在主线程)。 - 异步逻辑失效:
retrieve方法标记为async,但内部使用传统dataTask,await不会等待请求完成,导致singleCompany被提前赋值为nil。 - ViewModel声明错误:View中用
@State持有ObservableObject实例,无法监听ViewModel的状态变化,导致UI不会刷新。 - 缺少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
相关产品推荐
相关产品推荐

