在 swift 中恢复购买时崩溃
Crash when restoring purchases in swift
我 运行 对我的应用内购买恢复功能感到困惑。目前,我将恢复功能链接到一个按钮,当我多次激活它时它似乎崩溃了。例如,如果我点击它,恢复,导航到另一个视图,然后返回再次点击恢复它会崩溃。
任何人都可以检查我的代码,看看我是否遗漏了一些盯着我看的东西?
import SpriteKit
import StoreKit
class PurchaseView: SKScene, SKPaymentTransactionObserver, SKProductsRequestDelegate{
var instructLabel = SKLabelNode()
var priceLabel = SKLabelNode()
var saleBadgeIcon = SKSpriteNode()
var backIcon = SKSpriteNode()
var restoreIcon = SKSpriteNode()
var blueDiceDemo = SKSpriteNode()
var redDiceDemo = SKSpriteNode()
var greenDiceDemo = SKSpriteNode()
var grayDiceDemo = SKSpriteNode()
var bluePID: String = "dice.blue.add"
var redPID: String = "dice.red.add"
var greenPID: String = "dice.green.add"
var grayPID: String = "dice.gray.add"
private var request : SKProductsRequest!
private var products : [SKProduct] = []
private var blueDicePurchased : Bool = false
private var redDicePurchased : Bool = false
private var greenDicePurchased : Bool = false
private var grayDicePurchased : Bool = false
override func didMoveToView(view: SKView) {
// In-App Purchase
initInAppPurchases()
/*
checkAndActivateGreenColor()
checkAndActivateRedColor()
checkAndActivateGrayColor()
checkAndActivateBlueColor()
*/
createInstructionLabel()
createBackIcon()
createRestoreIcon()
createBlueDicePurchase()
createRedDicePurchase()
createGreenDicePurchase()
createGrayDicePurchase()
checkAndActivateDiceColor(bluePID)
checkAndActivateDiceColor(redPID)
checkAndActivateDiceColor(greenPID)
checkAndActivateDiceColor(grayPID)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (node == backIcon) {
let gameScene = GameScene(size: self.size)
let transition = SKTransition.doorsCloseVerticalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.ResizeFill
gameScene.backgroundColor = SKColor.whiteColor()
self.scene!.view?.presentScene(gameScene, transition: transition)
} else if (node == restoreIcon) {
print("restore my purchases")
let alert = UIAlertController(title: "Restore Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Restore", style: UIAlertActionStyle.Default) { _ in
self.restorePurchasedProducts()
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
//restorePurchasedProducts()
} else if (node == blueDiceDemo) {
print("buy blue")
if (!blueDicePurchased) {
inAppPurchase(blueDicePurchased, pid: bluePID)
}
} else if (node == redDiceDemo) {
print("buy red")
if (!redDicePurchased) {
inAppPurchase(redDicePurchased, pid: redPID)
}
} else if (node == greenDiceDemo) {
print("buy green")
if (!greenDicePurchased) {
inAppPurchase(greenDicePurchased, pid: greenPID)
}
} else if (node == grayDiceDemo) {
print("buy gray")
if (!grayDicePurchased) {
inAppPurchase(grayDicePurchased, pid: grayPID)
}
}
}
}
func createBlueDicePurchase() {
blueDiceDemo = SKSpriteNode(imageNamed: "dice1_blue")
blueDiceDemo.setScale(0.6)
blueDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) + blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(blueDiceDemo)
createSaleBadge(blueDiceDemo)
}
func createGrayDicePurchase() {
grayDiceDemo = SKSpriteNode(imageNamed: "dice1_gray")
grayDiceDemo.setScale(0.6)
grayDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
addChild(grayDiceDemo)
createSaleBadge(grayDiceDemo)
}
func createRedDicePurchase() {
redDiceDemo = SKSpriteNode(imageNamed: "dice1_red")
redDiceDemo.setScale(0.6)
redDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) - blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(redDiceDemo)
createSaleBadge(redDiceDemo)
}
func createGreenDicePurchase() {
greenDiceDemo = SKSpriteNode(imageNamed: "dice1_green")
greenDiceDemo.setScale(0.6)
greenDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - blueDiceDemo.size.height * 1.5)
addChild(greenDiceDemo)
createSaleBadge(greenDiceDemo)
}
func createInstructionLabel() {
instructLabel = SKLabelNode(fontNamed: "Helvetica")
instructLabel.text = "Click item to purchase!"
instructLabel.fontSize = 24
instructLabel.fontColor = SKColor.blackColor()
instructLabel.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMaxY(self.frame) - 50)
addChild(instructLabel)
}
func createPurchasedLabel(node: SKSpriteNode) {
let purchasedLabel = SKLabelNode(fontNamed: "Helvetica")
purchasedLabel.text = "purchased"
purchasedLabel.fontSize = 30
purchasedLabel.zPosition = 2
purchasedLabel.fontColor = SKColor.blackColor()
purchasedLabel.position = CGPoint(x: 0, y: -7.5)
node.addChild(purchasedLabel)
}
func createRestoreIcon() {
restoreIcon = SKSpriteNode(imageNamed: "download")
restoreIcon.setScale(0.4)
restoreIcon.position = CGPoint(x: CGRectGetMinX(self.frame) + 30, y: CGRectGetMinY(self.frame) + 30)
addChild(restoreIcon)
}
func createBackIcon() {
backIcon = SKSpriteNode(imageNamed: "remove")
backIcon.setScale(0.5)
backIcon.position = CGPoint(x: CGRectGetMaxX(self.frame) - 30, y: CGRectGetMinY(self.frame) + 30)
addChild(backIcon)
}
func createSaleBadge(node: SKSpriteNode) {
saleBadgeIcon = SKSpriteNode(imageNamed: "badge")
saleBadgeIcon.setScale(0.4)
saleBadgeIcon.zPosition = 2
saleBadgeIcon.position = CGPoint(x: node.size.width/2, y: node.size.height/2)
node.addChild(saleBadgeIcon)
}
func inAppPurchase(dicePurchased: Bool, pid: String) {
let alert = UIAlertController(title: "In-App Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
// Add an alert action for each available product
for (var i = 0; i < products.count; i++) {
let currentProduct = products[i]
if (currentProduct.productIdentifier == pid && !dicePurchased) {
// Get the localized price
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = currentProduct.priceLocale
// Add the alert action
alert.addAction(UIAlertAction(title: currentProduct.localizedTitle + " " + numberFormatter.stringFromNumber(currentProduct.price)!, style: UIAlertActionStyle.Default) { _ in
// Perform the purchase
self.buyProduct(currentProduct)
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
}
}
//Initializes the App Purchases
func initInAppPurchases() {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
// Get the list of possible purchases
if self.request == nil {
self.request = SKProductsRequest(productIdentifiers: Set(["dice.green.add", "dice.blue.add", "dice.gray.add","dice.red.add"]))
self.request.delegate = self
self.request.start()
}
}
// Request a purchase
func buyProduct(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
// Restore purchases
func restorePurchasedProducts() {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
// StoreKit protocoll method. Called when the AppStore responds
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
self.products = response.products
self.request = nil
}
// StoreKit protocoll method. Called when an error happens in the communication with the AppStore
func request(request: SKRequest, didFailWithError error: NSError) {
print(error)
self.request = nil
}
// StoreKit protocoll method. Called after the purchase
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .Purchased:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("buying green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("buying blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("buying red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("buying gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Restored:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("restoring green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("restoring blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("restoring red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("restoring gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Failed:
print("Payment Error: \(transaction.error)")
queue.finishTransaction(transaction)
default:
print("Transaction State: \(transaction.transactionState)")
}
}
}
// Called after the purchase to provide the colored dice feature
func handleDiceColorPurchase(pid: String){
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
checkAndActivateDiceColor(pid)
// persist the purchase locally
NSUserDefaults.standardUserDefaults().setBool(true, forKey: pid)
}
func checkAndActivateDiceColor(pid: String){
if NSUserDefaults.standardUserDefaults().boolForKey(pid) {
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
}
}
}
当它崩溃时,没有太多我可以破译的信息。我收到一个错误
stating EXC_BAD_ACCESS (code=1, address=0xc)
在我的 AppDelegate
class 上,一些突出显示的绿色内容表示从
入队
com.apple.root.default-qos.overcommit(Thread 4)
感谢任何帮助!
你的代码有点乱,我们看一下
1) 将您的 NSUserDefaults 密钥和产品 ID 放入 class 之上的结构中,这样您就可以避免拼写错误。
struct ProductID {
static let diceGrayAdd = "dice.gray.add"
....
}
然后像这样得到它
....payment.productIdentifier == ProductID.diceGrayAdd {
2) 在请求产品之前,您没有检查是否可以实际付款。
guard SKPaymentQueue.canMakePayments() else {
// show alert that IAPs are not enabled
return
}
3) 为什么要在委托方法中将请求设置为 nil?这是没有意义的。删除代码中的所有这些行
self.request = nil
4) 你也应该在 .Restore 的情况下使用 originalTransaction ,你的方式不太正确。不幸的是,很多教程都没有教你这个。
case .Restored:
/// Its an optional so safely unwrap it first
if let originalTransaction = transaction.originalTransaction {
if originalTransaction.payment.productIdentifier == ProductID.diceGrayAdd {
handleDiceColorPurchase(greenPID)
print("restoring green")
}
....
}
您还可以通过将解锁操作放入另一个函数来使您的代码更简洁一些,这样您就不必在 .Purchased 和 .Restored 情况下编写重复代码。
查看我最近为此发布的回答。您还应该处理 .Failed 案例中的错误。
5) 此外,当您离开商店时,您应该致电
requests.cancel()
以确保您不会在请求过程中更改 viewController。在我的 spriteKit 游戏中导致我崩溃,所以最好把它放在那里以确保它被取消。
6) 你是在呼叫这条线吗
SKPaymentQueue.default().remove(self)
这应该在您关闭应用程序时被调用,或者在您的情况下可能在您退出商店时被调用。这确保所有事务都从观察者中删除,并且将来不会以登录消息的形式出现。
让我知道这是否解决了您的崩溃问题。
谢谢!对我也有用!!!
提到的崩溃也发生在我身上。上面的答案很完整,但是,仅最后一条评论就解决了崩溃问题。
我在Objective-C中使用的代码:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
感谢@crashoverride777 (!!!) 添加 (Swift 4):
后完全相同的问题得到解决
override func viewDidDisappear(_ animated: Bool) {
SKPaymentQueue.default().remove(self)
}
我 运行 对我的应用内购买恢复功能感到困惑。目前,我将恢复功能链接到一个按钮,当我多次激活它时它似乎崩溃了。例如,如果我点击它,恢复,导航到另一个视图,然后返回再次点击恢复它会崩溃。
任何人都可以检查我的代码,看看我是否遗漏了一些盯着我看的东西?
import SpriteKit
import StoreKit
class PurchaseView: SKScene, SKPaymentTransactionObserver, SKProductsRequestDelegate{
var instructLabel = SKLabelNode()
var priceLabel = SKLabelNode()
var saleBadgeIcon = SKSpriteNode()
var backIcon = SKSpriteNode()
var restoreIcon = SKSpriteNode()
var blueDiceDemo = SKSpriteNode()
var redDiceDemo = SKSpriteNode()
var greenDiceDemo = SKSpriteNode()
var grayDiceDemo = SKSpriteNode()
var bluePID: String = "dice.blue.add"
var redPID: String = "dice.red.add"
var greenPID: String = "dice.green.add"
var grayPID: String = "dice.gray.add"
private var request : SKProductsRequest!
private var products : [SKProduct] = []
private var blueDicePurchased : Bool = false
private var redDicePurchased : Bool = false
private var greenDicePurchased : Bool = false
private var grayDicePurchased : Bool = false
override func didMoveToView(view: SKView) {
// In-App Purchase
initInAppPurchases()
/*
checkAndActivateGreenColor()
checkAndActivateRedColor()
checkAndActivateGrayColor()
checkAndActivateBlueColor()
*/
createInstructionLabel()
createBackIcon()
createRestoreIcon()
createBlueDicePurchase()
createRedDicePurchase()
createGreenDicePurchase()
createGrayDicePurchase()
checkAndActivateDiceColor(bluePID)
checkAndActivateDiceColor(redPID)
checkAndActivateDiceColor(greenPID)
checkAndActivateDiceColor(grayPID)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (node == backIcon) {
let gameScene = GameScene(size: self.size)
let transition = SKTransition.doorsCloseVerticalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.ResizeFill
gameScene.backgroundColor = SKColor.whiteColor()
self.scene!.view?.presentScene(gameScene, transition: transition)
} else if (node == restoreIcon) {
print("restore my purchases")
let alert = UIAlertController(title: "Restore Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Restore", style: UIAlertActionStyle.Default) { _ in
self.restorePurchasedProducts()
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
//restorePurchasedProducts()
} else if (node == blueDiceDemo) {
print("buy blue")
if (!blueDicePurchased) {
inAppPurchase(blueDicePurchased, pid: bluePID)
}
} else if (node == redDiceDemo) {
print("buy red")
if (!redDicePurchased) {
inAppPurchase(redDicePurchased, pid: redPID)
}
} else if (node == greenDiceDemo) {
print("buy green")
if (!greenDicePurchased) {
inAppPurchase(greenDicePurchased, pid: greenPID)
}
} else if (node == grayDiceDemo) {
print("buy gray")
if (!grayDicePurchased) {
inAppPurchase(grayDicePurchased, pid: grayPID)
}
}
}
}
func createBlueDicePurchase() {
blueDiceDemo = SKSpriteNode(imageNamed: "dice1_blue")
blueDiceDemo.setScale(0.6)
blueDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) + blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(blueDiceDemo)
createSaleBadge(blueDiceDemo)
}
func createGrayDicePurchase() {
grayDiceDemo = SKSpriteNode(imageNamed: "dice1_gray")
grayDiceDemo.setScale(0.6)
grayDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
addChild(grayDiceDemo)
createSaleBadge(grayDiceDemo)
}
func createRedDicePurchase() {
redDiceDemo = SKSpriteNode(imageNamed: "dice1_red")
redDiceDemo.setScale(0.6)
redDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) - blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(redDiceDemo)
createSaleBadge(redDiceDemo)
}
func createGreenDicePurchase() {
greenDiceDemo = SKSpriteNode(imageNamed: "dice1_green")
greenDiceDemo.setScale(0.6)
greenDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - blueDiceDemo.size.height * 1.5)
addChild(greenDiceDemo)
createSaleBadge(greenDiceDemo)
}
func createInstructionLabel() {
instructLabel = SKLabelNode(fontNamed: "Helvetica")
instructLabel.text = "Click item to purchase!"
instructLabel.fontSize = 24
instructLabel.fontColor = SKColor.blackColor()
instructLabel.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMaxY(self.frame) - 50)
addChild(instructLabel)
}
func createPurchasedLabel(node: SKSpriteNode) {
let purchasedLabel = SKLabelNode(fontNamed: "Helvetica")
purchasedLabel.text = "purchased"
purchasedLabel.fontSize = 30
purchasedLabel.zPosition = 2
purchasedLabel.fontColor = SKColor.blackColor()
purchasedLabel.position = CGPoint(x: 0, y: -7.5)
node.addChild(purchasedLabel)
}
func createRestoreIcon() {
restoreIcon = SKSpriteNode(imageNamed: "download")
restoreIcon.setScale(0.4)
restoreIcon.position = CGPoint(x: CGRectGetMinX(self.frame) + 30, y: CGRectGetMinY(self.frame) + 30)
addChild(restoreIcon)
}
func createBackIcon() {
backIcon = SKSpriteNode(imageNamed: "remove")
backIcon.setScale(0.5)
backIcon.position = CGPoint(x: CGRectGetMaxX(self.frame) - 30, y: CGRectGetMinY(self.frame) + 30)
addChild(backIcon)
}
func createSaleBadge(node: SKSpriteNode) {
saleBadgeIcon = SKSpriteNode(imageNamed: "badge")
saleBadgeIcon.setScale(0.4)
saleBadgeIcon.zPosition = 2
saleBadgeIcon.position = CGPoint(x: node.size.width/2, y: node.size.height/2)
node.addChild(saleBadgeIcon)
}
func inAppPurchase(dicePurchased: Bool, pid: String) {
let alert = UIAlertController(title: "In-App Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
// Add an alert action for each available product
for (var i = 0; i < products.count; i++) {
let currentProduct = products[i]
if (currentProduct.productIdentifier == pid && !dicePurchased) {
// Get the localized price
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = currentProduct.priceLocale
// Add the alert action
alert.addAction(UIAlertAction(title: currentProduct.localizedTitle + " " + numberFormatter.stringFromNumber(currentProduct.price)!, style: UIAlertActionStyle.Default) { _ in
// Perform the purchase
self.buyProduct(currentProduct)
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
}
}
//Initializes the App Purchases
func initInAppPurchases() {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
// Get the list of possible purchases
if self.request == nil {
self.request = SKProductsRequest(productIdentifiers: Set(["dice.green.add", "dice.blue.add", "dice.gray.add","dice.red.add"]))
self.request.delegate = self
self.request.start()
}
}
// Request a purchase
func buyProduct(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
// Restore purchases
func restorePurchasedProducts() {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
// StoreKit protocoll method. Called when the AppStore responds
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
self.products = response.products
self.request = nil
}
// StoreKit protocoll method. Called when an error happens in the communication with the AppStore
func request(request: SKRequest, didFailWithError error: NSError) {
print(error)
self.request = nil
}
// StoreKit protocoll method. Called after the purchase
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .Purchased:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("buying green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("buying blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("buying red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("buying gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Restored:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("restoring green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("restoring blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("restoring red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("restoring gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Failed:
print("Payment Error: \(transaction.error)")
queue.finishTransaction(transaction)
default:
print("Transaction State: \(transaction.transactionState)")
}
}
}
// Called after the purchase to provide the colored dice feature
func handleDiceColorPurchase(pid: String){
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
checkAndActivateDiceColor(pid)
// persist the purchase locally
NSUserDefaults.standardUserDefaults().setBool(true, forKey: pid)
}
func checkAndActivateDiceColor(pid: String){
if NSUserDefaults.standardUserDefaults().boolForKey(pid) {
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
}
}
}
当它崩溃时,没有太多我可以破译的信息。我收到一个错误
stating EXC_BAD_ACCESS (code=1, address=0xc)
在我的 AppDelegate
class 上,一些突出显示的绿色内容表示从
com.apple.root.default-qos.overcommit(Thread 4)
感谢任何帮助!
你的代码有点乱,我们看一下
1) 将您的 NSUserDefaults 密钥和产品 ID 放入 class 之上的结构中,这样您就可以避免拼写错误。
struct ProductID {
static let diceGrayAdd = "dice.gray.add"
....
}
然后像这样得到它
....payment.productIdentifier == ProductID.diceGrayAdd {
2) 在请求产品之前,您没有检查是否可以实际付款。
guard SKPaymentQueue.canMakePayments() else {
// show alert that IAPs are not enabled
return
}
3) 为什么要在委托方法中将请求设置为 nil?这是没有意义的。删除代码中的所有这些行
self.request = nil
4) 你也应该在 .Restore 的情况下使用 originalTransaction ,你的方式不太正确。不幸的是,很多教程都没有教你这个。
case .Restored:
/// Its an optional so safely unwrap it first
if let originalTransaction = transaction.originalTransaction {
if originalTransaction.payment.productIdentifier == ProductID.diceGrayAdd {
handleDiceColorPurchase(greenPID)
print("restoring green")
}
....
}
您还可以通过将解锁操作放入另一个函数来使您的代码更简洁一些,这样您就不必在 .Purchased 和 .Restored 情况下编写重复代码。
查看我最近为此发布的回答。您还应该处理 .Failed 案例中的错误。
5) 此外,当您离开商店时,您应该致电
requests.cancel()
以确保您不会在请求过程中更改 viewController。在我的 spriteKit 游戏中导致我崩溃,所以最好把它放在那里以确保它被取消。
6) 你是在呼叫这条线吗
SKPaymentQueue.default().remove(self)
这应该在您关闭应用程序时被调用,或者在您的情况下可能在您退出商店时被调用。这确保所有事务都从观察者中删除,并且将来不会以登录消息的形式出现。
让我知道这是否解决了您的崩溃问题。
谢谢!对我也有用!!! 提到的崩溃也发生在我身上。上面的答案很完整,但是,仅最后一条评论就解决了崩溃问题。
我在Objective-C中使用的代码:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
感谢@crashoverride777 (!!!) 添加 (Swift 4):
后完全相同的问题得到解决 override func viewDidDisappear(_ animated: Bool) {
SKPaymentQueue.default().remove(self)
}