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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:03:37