iOS 15.2中IAP订阅崩溃:布局引擎线程异常求助
iOS 15.2 内购(IAP)崩溃问题修复方案
问题背景
已完成内购(IAP)集成(含Signing & Capabilities添加IAP能力、App Store Connect配置订阅服务),代码在iOS 13运行正常,但iOS 15.2触发崩溃,报错信息:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
已尝试添加DispatchQueue.main.async但未解决,原代码如下:
import StoreKit import UIKit class BuyProfileBoosterVC: UIViewController, SKPaymentTransactionObserver, SKProductsRequestDelegate { var products = [SKProduct]() func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { products = response.products showSingleButtonWithMessage(title: "Alert", message: "You have total \(products.count) product in App Store connect.", buttonName: "Okay") } override func viewDidLoad() { super.viewDidLoad() DispatchQueue.main.async{ SKPaymentQueue.default().add(self) } } enum Products: String, CaseIterable { case threeDays } // MARK: - Popup related func fetchProduct() { let request = SKProductsRequest(productIdentifiers: Set(Products.allCases.compactMap({$0.rawValue}))) request.delegate = self request.start() } func buyProduct(productId: String) { fetchProduct() DispatchQueue.main.async{ if SKPaymentQueue.canMakePayments() { let paymentRequest = SKMutablePayment() paymentRequest.productIdentifier = productId SKPaymentQueue.default().add(paymentRequest) } else { self.view.makeToast("You can't make payment.") } } } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { DispatchQueue.main.async { transactions.forEach { (transaction) in switch transaction.transactionState { case .purchasing: self.view.makeToast("Transaction started.") case .purchased, .restored: self.view.makeToast("Transaction successful/restored.") case .failed, .deferred: self.view.makeToast("Transaction failed.") @unknown default: self.view.makeToast("Unknown state.") } } } } @IBAction func jumpBuyAction(_ sender: UIButton) { DispatchQueue.main.async { self.fetchProduct() self.buyProduct(productId: "threeDay") } } }
崩溃原因
- StoreKit代理回调线程问题:
productsRequest(_:didReceive:)、paymentQueue(_:updatedTransactions:)默认在后台线程执行,原代码中productsRequest直接调用弹窗UI操作未切换主线程,这是崩溃核心触发点 - 逻辑冗余导致线程冲突:
buyProduct和jumpBuyAction中重复调用fetchProduct,且支付请求未等待产品获取完成就发起,加剧线程冲突风险 - 产品ID拼写不匹配:
buyProduct(productId: "threeDay")与枚举Products.threeDays的rawValue不一致,可能导致产品获取失败
修复方案
1. 强制UI操作在主线程执行
将所有代理回调中的弹窗、Toast操作包裹在DispatchQueue.main.async内
2. 修正内购逻辑
移除重复的fetchProduct调用,确保支付请求仅在产品获取完成后发起
3. 统一产品ID
修正枚举与调用参数的ID匹配问题
4. 完善内购流程
添加finishTransaction调用,避免交易残留引发内存泄漏
修改后的完整代码
import StoreKit import UIKit class BuyProfileBoosterVC: UIViewController, SKPaymentTransactionObserver, SKProductsRequestDelegate { var products = [SKProduct]() func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { products = response.products // 弹窗操作强制切主线程 DispatchQueue.main.async { self.showSingleButtonWithMessage(title: "Alert", message: "You have total \(self.products.count) product in App Store connect.", buttonName: "Okay") } } override func viewDidLoad() { super.viewDidLoad() // viewDidLoad本身在主线程,无需额外包裹async SKPaymentQueue.default().add(self) } // 统一产品ID,与调用参数匹配 enum Products: String, CaseIterable { case threeDays = "threeDay" } // MARK: - Popup related func fetchProduct() { let request = SKProductsRequest(productIdentifiers: Set(Products.allCases.compactMap({$0.rawValue}))) request.delegate = self request.start() } func buyProduct(productId: String) { DispatchQueue.main.async { guard SKPaymentQueue.canMakePayments() else { self.view.makeToast("You can't make payment.") return } // 确保产品已获取到再发起支付 guard self.products.contains(where: { $0.productIdentifier == productId }) else { self.view.makeToast("Product not found.") return } let paymentRequest = SKMutablePayment() paymentRequest.productIdentifier = productId SKPaymentQueue.default().add(paymentRequest) } } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { DispatchQueue.main.async { transactions.forEach { transaction in switch transaction.transactionState { case .purchasing: self.view.makeToast("Transaction started.") case .purchased, .restored: self.view.makeToast("Transaction successful/restored.") SKPaymentQueue.default().finishTransaction(transaction) case .failed, .deferred: self.view.makeToast("Transaction failed.") SKPaymentQueue.default().finishTransaction(transaction) @unknown default: self.view.makeToast("Unknown state.") SKPaymentQueue.default().finishTransaction(transaction) } } } } @IBAction func jumpBuyAction(_ sender: UIButton) { // 先获取产品,可根据业务需求在productsRequest回调中自动触发购买 fetchProduct() } }
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

