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

如何在SwiftUI中等待StoreKit2产品加载的异步函数结果?

SwiftUI中正确等待StoreKit2异步加载结果的方案

首先指出你原代码的问题:在View的init里直接调用async throws函数是错误的——init是同步方法,既无法等待异步任务完成,也没法处理抛出的错误,而且直接在初始化阶段操作@State变量可能引发SwiftUI视图状态异常。

下面是两种靠谱的实现方案:

方案一:使用.task修饰符(SwiftUI 3+推荐)

.task是SwiftUI专门为异步任务设计的修饰符,会在视图出现时自动执行异步逻辑,视图消失时自动取消任务,还能自然处理错误:

struct SettingsForm : View {
    @State var selectedRegion: Int = 0
    @State var alwaysOriginalTitle: Bool = false
    @State private var products: [Product] = []
    @State private var loadError: Error? // 存储加载错误
    @Environment(\.dismiss) var dismiss // 替代旧的presentationMode
    let productIds = ["premium"]

    var body: some View {
        Form {
            // 你的原有表单控件...
            
            // 根据加载状态展示不同内容
            if let error = loadError {
                Text("产品加载失败:\(error.localizedDescription)")
                    .foregroundColor(.red)
            } else if products.isEmpty {
                ProgressView("加载中...")
            } else {
                // 加载完成后展示产品
                ForEach(products, id: \.id) { product in
                    Text("\(product.displayName) - \(product.displayPrice)")
                }
            }
        }
        .task {
            do {
                // 等待异步加载完成并赋值
                products = try await Product.products(for: productIds)
                print(products)
            } catch {
                // 捕获并存储错误
                loadError = error
                print("加载出错:\(error)")
            }
        }
    }
}

方案二:分离逻辑到ViewModel(复杂场景适用)

如果需要更复杂的状态管理,把加载逻辑抽离到ObservableObject类型的ViewModel中,符合MVVM架构:

class StoreViewModel: ObservableObject {
    @Published var products: [Product] = []
    @Published var loadError: Error?
    let productIds = ["premium"]

    func loadProducts() async {
        do {
            products = try await Product.products(for: productIds)
            print(products)
        } catch {
            loadError = error
            print("加载出错:\(error)")
        }
    }
}

struct SettingsForm : View {
    @State var selectedRegion: Int = 0
    @State var alwaysOriginalTitle: Bool = false
    @StateObject private var storeVM = StoreViewModel()
    @Environment(\.dismiss) var dismiss

    var body: some View {
        Form {
            // 你的原有表单控件...
            
            if let error = storeVM.loadError {
                Text("产品加载失败:\(error.localizedDescription)")
                    .foregroundColor(.red)
            } else if storeVM.products.isEmpty {
                ProgressView("加载中...")
            } else {
                ForEach(storeVM.products, id: \.id) { product in
                    Text("\(product.displayName) - \(product.displayPrice)")
                }
            }
        }
        .task {
            await storeVM.loadProducts()
        }
    }
}

关键注意点

  • 永远不要在View的初始化方法里调用异步函数,SwiftUI视图是值类型,初始化阶段操作状态会引发不可预期的问题。
  • Product.products(for:)会抛出错误(比如网络异常、产品ID无效),必须用do-catch捕获处理。
  • 加载过程中要给用户反馈(比如ProgressView),避免界面无响应感。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:31:16