Swift中多In-App Purchase管理问题求助及示例代码需求
多IAP产品管理完整解决方案(修复恢复失效问题)
我来帮你搞定多IAP的管理问题,先揪出你当前代码里的几个核心坑,再给你完整的优化示例:
问题分析
- 恢复逻辑错误:你在
paymentQueueRestoreCompletedTransactionsFinished里遍历的是queue.transactions,这是队列里的所有交易,不是苹果返回的恢复交易集合;而且updatedTransactions的.restored分支完全是空的,没处理恢复后的状态更新。 - UserDefaults判断冗余:原代码里
(UserDefaults.standard.value(forKey: "...") != nil) == true的写法太绕,直接用bool(forKey:)更简洁,因为未设置时默认返回false。 - 重复添加观察者:每个购买按钮和恢复按钮里都调用
SKPaymentQueue.default().add(self),会导致重复注册观察者,引发多次回调。
修正后的完整代码
import UIKit import StoreKit class IAPViewController: UIViewController, SKPaymentTransactionObserver { // MARK: - UI组件 @IBOutlet weak var product1BuyButton: UIButton! @IBOutlet weak var product1Image: UIImageView! @IBOutlet weak var product2BuyButton: UIButton! @IBOutlet weak var product2Image: UIImageView! // MARK: - 产品ID常量(建议统一管理) private let productID1 = "example.productID1" private let productID2 = "example.productID2" override func viewDidLoad() { super.viewDidLoad() // 只在初始化时添加一次交易观察者 SKPaymentQueue.default().add(self) // 页面加载时就检查已购买状态,更新UI updatePurchaseUI() } deinit { // 页面销毁时移除观察者,避免内存泄漏 SKPaymentQueue.default().remove(self) } // MARK: - 购买按钮点击事件 @IBAction func didTapBuyPRODUCT1(_ sender: Any) { purchaseProduct(with: productID1) } @IBAction func didTapBuyPRODUCT2(_ sender: Any) { purchaseProduct(with: productID2) } // MARK: - 通用购买方法(抽离重复逻辑) private func purchaseProduct(with productID: String) { guard SKPaymentQueue.canMakePayments() else { // 提示用户无法进行购买(比如家长控制开启) let alert = UIAlertController(title: "无法购买", message: "请检查设备的购买权限设置", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) return } let payment = SKMutablePayment(productIdentifier: productID) SKPaymentQueue.default().add(payment) } // MARK: - 恢复按钮点击事件 @IBAction func restoreButton(_ sender: Any) { SKPaymentQueue.default().restoreCompletedTransactions() } // MARK: - SKPaymentTransactionObserver 核心回调 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchased: handlePurchasedTransaction(transaction) queue.finishTransaction(transaction) case .restored: handleRestoredTransaction(transaction) queue.finishTransaction(transaction) case .failed: if let error = transaction.error as? SKError { print("购买失败: \(error.localizedDescription)") // 处理用户取消购买的情况 if error.code == .paymentCancelled { print("用户取消了购买") } } queue.finishTransaction(transaction) case .purchasing: print("正在处理购买...") case .deferred: print("购买等待家长批准...") @unknown default: print("未知交易状态") queue.finishTransaction(transaction) } } } // MARK: - 处理已购买交易 private func handlePurchasedTransaction(_ transaction: SKPaymentTransaction) { let productID = transaction.payment.productIdentifier switch productID { case productID1: UserDefaults.standard.set(true, forKey: "product1purchased") case productID2: UserDefaults.standard.set(true, forKey: "product2purchased") default: print("未知产品ID: \(productID)") } updatePurchaseUI() showSuccessAlert() } // MARK: - 处理恢复交易 private func handleRestoredTransaction(_ transaction: SKPaymentTransaction) { // 恢复交易的处理逻辑和购买完全一致 handlePurchasedTransaction(transaction) } // MARK: - 恢复完成回调(可选:可以在这里统一提示恢复结果) func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { if queue.transactions.isEmpty { showAlert(title: "恢复完成", message: "没有已购买的项目需要恢复") } else { showAlert(title: "恢复完成", message: "已恢复所有已购买项目") } } // MARK: - 更新购买状态UI private func updatePurchaseUI() { // 产品1状态更新 let isProduct1Purchased = UserDefaults.standard.bool(forKey: "product1purchased") product1Image.image = UIImage(named: isProduct1Purchased ? "Product 1 In-App Purchase Logo PURCHASED" : "Product 1 In-App Purchase NOT PURCHASED") product1BuyButton.isEnabled = !isProduct1Purchased product1BuyButton.setTitle(isProduct1Purchased ? "Purchased" : "Buy", for: .normal) // 产品2状态更新 let isProduct2Purchased = UserDefaults.standard.bool(forKey: "product2purchased") product2Image.image = UIImage(named: isProduct2Purchased ? "Product 2 In-App Purchase Logo PURCHASED" : "Product 2 In-App Purchase NOT PURCHASED") product2BuyButton.isEnabled = !isProduct2Purchased product2BuyButton.setTitle(isProduct2Purchased ? "Purchased" : "Buy", for: .normal) } // MARK: - 通用弹窗提示 private func showSuccessAlert() { showAlert(title: "购买成功", message: "已解锁对应功能") } private func showAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) present(alert, animated: true) } }
关键优化点说明
- 统一管理产品ID:把产品ID抽成常量,避免硬编码出错,后期修改更方便。
- 抽离通用逻辑:把购买、UI更新、弹窗等重复逻辑封装成方法,代码更简洁易维护。
- 正确处理恢复交易:在
.restored分支调用和购买相同的状态更新逻辑,确保恢复后能正确解锁功能;paymentQueueRestoreCompletedTransactionsFinished只用来提示恢复结果,不用再处理状态。 - 优化观察者生命周期:在
viewDidLoad添加观察者,deinit移除,避免重复注册和内存泄漏。 - 简化UserDefaults判断:直接用
bool(forKey:)获取状态,逻辑更清晰。
内容的提问来源于stack exchange,提问作者mt0000
相关产品推荐
相关产品推荐

