SwiftUI中task使用优化:解决fetchProducts重复调用问题
问题分析与优化方案
核心原因
fetchProducts() 被多次调用的主要原因是:
- SwiftUI 的视图
body会因状态变化(如products更新、alert显示/隐藏)重复计算,导致.task修饰符多次触发 - 旧版
NavigationView在部分场景下存在重复渲染的问题 - 缺乏请求去重逻辑,无法阻止重复发起网络请求
具体优化步骤
1. 给 ViewModel 添加请求去重逻辑
在 ViewModel 中新增加载状态,确保同一时间只发起一次请求:
@MainActor class ProductViewModel: ObservableObject { @Published var products: [Product] = [] @Published var showError = false @Published var productError: ProductError? @Published var isLoading = false // 新增加载状态标记 private let provider: ProductProvider init(provider: ProductProvider = ProductProvider()) { self.provider = provider } func fetchProducts() async throws { guard !isLoading else { return } // 重复请求直接返回 isLoading = true defer { isLoading = false } // 请求完成后重置状态 do { self.products = try await provider.products } catch let error as ProductError { productError = error showError = true } catch { productError = .unknown // 补充处理非自定义错误 showError = true } } }
2. 替换 NavigationView 为 NavigationStack(iOS 16+)
NavigationView 在 iOS 16 后已被废弃,NavigationStack 的渲染逻辑更稳定,能减少不必要的视图刷新:
struct ProductsView: View { @EnvironmentObject var viewModel: ProductViewModel var body: some View { NavigationStack { // 替换原NavigationView List(viewModel.products, id: \.self) { product in ProductRow(product: product) } .padding() } .task { try? await viewModel.fetchProducts() } .alert(isPresented: $viewModel.showError) { Alert( title: Text("Error"), message: Text(viewModel.productError?.localizedDescription ?? "Unknown error"), dismissButton: .default(Text("OK")) ) } } }
3. 优化 Alert 绑定逻辑
原代码中使用 $viewModel.productError.wrappedValue 可能触发额外视图刷新,直接访问属性即可避免此问题(已在上面的代码中同步修改)。
额外说明
- 请求去重逻辑是解决重复调用问题的核心,无论视图如何刷新,都能确保同一时间只有一个请求在执行
- 若需兼容 iOS 15 及以下版本,可保留
NavigationView,但建议在ProductsView外层添加@State private var didFetch = false,结合.onAppear控制仅请求一次:.onAppear { guard !didFetch else { return } didFetch = true Task { try? await viewModel.fetchProducts() } }
内容的提问来源于stack exchange,提问作者mars_dev
相关产品推荐
相关产品推荐

