SwiftUI onAppear内嵌套网络请求无法获取数据问题
问题原因
列表无法加载数据由4个核心问题导致:
- 异步时序错误:网络请求是异步非阻塞逻辑,调用
getDataModelApi()后代码不会等待请求返回,会立刻执行后续的for循环。此时一级接口还未响应,存储子链接的数组为空,循环不会执行,根本不会触发子接口数据拉取。 - 属性引用错误:
getDataModelApi()解码后赋值的是Api类的modelApiLink属性,但视图层遍历的是modelApiLink.models属性,两个属性完全不对应,就算一级请求返回也拿不到子链接列表。 - 数据覆盖问题:如果
getData(url:)逻辑和getDataModelApi()完全一致,会将解码结果整体赋值给models数组,后返回的子接口数据会覆盖之前的内容,最终数组只会保留最后一个接口的返回值,无法聚合所有业务数据。 - 冗余实例问题:创建两个独立的
Api实例拆分逻辑没有必要,反而增加了跨实例状态同步的成本,额外的命名不规范(类型名用小驼峰model、页面名拼写错误detiles)也容易触发隐性问题。
修复方案
推荐使用Swift原生async/await语法控制异步流程(适配iOS15+),从根源避免回调地狱与时序混乱,同时修正逻辑问题:
- 重构
Api类,统一管理网络逻辑,消除重复代码,修正数据存储逻辑
// 注意:Swift类型名使用大驼峰命名规范 struct Model: Identifiable, Decodable { let id: Int let title: String let url: String } class Api : ObservableObject{ // 存储一级接口返回的子API链接 @Published var apiLinks: [Model] = [] // 存储所有子接口返回的聚合业务数据 @Published var businessModels: [Model] = [] // 统一管理鉴权信息,避免重复代码 private let token = "38|Xxxxxxx" private func buildAuthorizedRequest(for urlString: String) -> URLRequest? { guard let url = URL(string: urlString) else { return nil } var request = URLRequest(url: url) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") return request } // 拉取一级接口的子链接列表 private func fetchApiLinks() async { guard let request = buildAuthorizedRequest(for: APIgetURL.demo) else { return } do { let (data, _) = try await URLSession.shared.data(for: request) let decodedLinks = try JSONDecoder().decode([Model].self, from: data) await MainActor.run { self.apiLinks = decodedLinks } } catch { print("拉取子链接失败: ", error) } } // 拉取单个子接口的业务数据 private func fetchSingleBusinessData(url: String) async { guard let request = buildAuthorizedRequest(for: url) else { return } do { let (data, _) = try await URLSession.shared.data(for: request) let decodedModel = try JSONDecoder().decode(Model.self, from: data) await MainActor.run { // 如需去重可在此处加判断逻辑 self.businessModels.append(decodedModel) } } catch { print("拉取子接口\(url)数据失败: ", error) } } // 对外暴露的统一加载方法:先拉链接,再并行拉取所有业务数据 func loadAllContent() async { // 等待子链接拉取完成后再执行后续逻辑 await fetchApiLinks() // 用任务组并行拉取所有子接口数据,比串行请求速度更快 await withTaskGroup(of: Void.self) { group in for link in apiLinks { group.addTask { await self.fetchSingleBusinessData(url: link.url) } } } } }
- 简化视图层逻辑,只保留一个
Api实例,用.task修饰符处理异步加载
struct VideoViewAll : View { // 只需要一个视图模型实例即可,无需创建多个 @StateObject private var viewModel = Api() var body: some View { VStack { ScrollView(.vertical, showsIndicators: false) { ForEach(viewModel.businessModels) { item in HStack { // 修正原拼写错误detiles -> Details NavigationLink(destination: Details(model: item)) { Spacer() VStack(alignment: .trailing, spacing: 15) { Text(item.title) } .padding(.trailing, 5) } } } } } // 视图加载时自动触发异步请求,视图销毁时自动取消任务,避免内存泄漏 .task { await viewModel.loadAllContent() } } } // 详情页 struct Details: View { let model: Model var body: some View { Text(model.title) } }
若需要兼容iOS14及更早版本无法使用async/await,可通过给网络方法增加逃逸闭包回调的方式控制时序:在
getDataModelApi的请求完成回调中遍历链接列表,再触发子接口请求;同时修正属性引用错误,将子接口返回的数据追加到models数组而非直接覆盖即可。
内容的提问来源于stack exchange,提问作者3sr0 store
相关产品推荐
相关产品推荐

