StoreKit2订阅状态监听优化:消除错误并统一更新逻辑
简洁统一的StoreKit 2订阅状态监听方案
核心方案
用全局ObservableObject管理器封装所有StoreKit逻辑,启动时监听交易更新消除错误提示,同时同步当前有效订阅状态;所有视图直接绑定管理器的可观察状态,自动更新,彻底移除onDisappear里的手动刷新逻辑。
具体实现
1. 订阅状态管理器
这个类统一处理交易监听、订阅状态同步,对外暴露可观察的当前有效订阅:
import StoreKit class SubscriptionManager: ObservableObject { // 当前生效的订阅状态(单订阅组,仅存一个) @Published var currentActiveSubscription: Product.SubscriptionInfo.Status? private let productIDs: [String] private let subscriptionGroupID: String init(productIDs: [String], subscriptionGroupID: String) { self.productIDs = productIDs self.subscriptionGroupID = subscriptionGroupID // 启动即开始监听 startTransactionListener() Task { await syncCurrentEntitlements() } } // 监听所有交易更新(解决错误提示的关键) private func startTransactionListener() { Task { for await result in Transaction.updates { do { let transaction = try result.payloadValue // 必须调用finish,否则会重复处理交易 await transaction.finish() // 交易完成后同步订阅状态 await syncCurrentEntitlements() } catch { print("交易处理失败: \(error.localizedDescription)") } } } } // 同步当前有效订阅(自动过滤过期/撤销的订阅) private func syncCurrentEntitlements() async { do { for await entitlement in Transaction.currentEntitlements { let transaction = try entitlement.payloadValue guard let subscription = transaction.product.subscription else { continue } // 只处理当前订阅组的订阅 guard subscription.subscriptionGroupID == self.subscriptionGroupID else { continue } // 获取当前活跃/宽限期内的订阅状态 let activeStatus = try await transaction.product.subscription?.status.first { $0.state == .subscribed || $0.state == .inGracePeriod } DispatchQueue.main.async { self.currentActiveSubscription = activeStatus } // 单订阅组,找到后直接终止循环 break } } catch { print("同步订阅状态失败: \(error.localizedDescription)") DispatchQueue.main.async { self.currentActiveSubscription = nil } } } // 可选:获取产品列表,供StoreView使用 func fetchProducts() async throws -> [Product] { return try await Product.products(for: productIDs) } }
2. App入口初始化管理器
在App启动时初始化管理器,注入到环境中全局可用:
@main struct YourApp: App { // 全局共享的订阅管理器 let subscriptionManager = SubscriptionManager( productIDs: ["com.yourapp.product1", "com.yourapp.product2"], subscriptionGroupID: "com.yourapp.subscription-group" ) var body: some Scene { WindowGroup { ContentView() .environmentObject(subscriptionManager) } } }
3. 视图中使用管理器状态
所有视图直接绑定管理器的currentActiveSubscription,自动更新状态,无需手动刷新:
struct ContentView: View { @EnvironmentObject var subscriptionManager: SubscriptionManager var body: some View { VStack { if let subscription = subscriptionManager.currentActiveSubscription { Text("当前订阅: \(subscription.productID)") Text("状态: \(subscription.state.rawValue)") } else { Text("无有效订阅") // 根据用户位置展示对应订阅视图 if isInTargetRegion { SubscriptionStoreView(groupID: subscriptionManager.subscriptionGroupID) } else { StoreView(ids: subscriptionManager.productIDs) } } } } // 示例:用户位置判断逻辑 private var isInTargetRegion: Bool { // 替换为你的实际位置判断代码 return true } }
4. 清理旧代码
直接删除所有视图onDisappear中手动获取订阅状态的代码,所有状态更新由管理器自动同步。
关键说明
Transaction.updates会监听应用内、应用外的所有交易变更(比如用户在系统设置中取消/续订),确保状态实时同步Transaction.currentEntitlements仅返回当前有效的订阅,自动过滤过期、已撤销的记录,解决旧方案中已购ID列表持续增长的问题- 必须调用
transaction.finish()完成交易,否则StoreKit会重复推送交易事件 - UI更新要切换到主队列,因为StoreKit回调默认在后台线程
内容的提问来源于stack exchange,提问作者Kyra
相关产品推荐
相关产品推荐

