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

SwiftUI中task使用优化:解决fetchProducts重复调用问题

问题分析与优化方案

核心原因

fetchProducts() 被多次调用的主要原因是:

  1. SwiftUI 的视图 body 会因状态变化(如 products 更新、alert 显示/隐藏)重复计算,导致 .task 修饰符多次触发
  2. 旧版 NavigationView 在部分场景下存在重复渲染的问题
  3. 缺乏请求去重逻辑,无法阻止重复发起网络请求

具体优化步骤

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:25:11