我的应用内购买恢复功能遇到了一些令人困惑的行为。目前,我将恢复功能链接到一个按钮,当我多次激活它时,它似乎崩溃了。例如,如果我点击它,恢复,导航到另一个视图,然后返回再次点击恢复它将崩溃。
任何人都可以检查我的代码,看看我是否遗漏了一些盯着我看的东西?
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")
}
}
}
}
当它崩溃时,我无法解读多少信息。我收到错误
说明EXC_BAD_ACCESS(代码= 1,地址= 0xc)
在我的AppDelegate
课程中,某些内容突出显示为绿色,表示已从
com.apple.root.default-qos.overcommit(主题4)
感谢任何帮助!
答案 0 :(得分:4)
你的代码有点乱,让我们通过它
1)将您的NSUserDefaults密钥和产品ID放入班级上方的结构中,这样可以避免拼写错误。
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案例中的错误。
Restore Purchase : Non-Consumable
5)当你离开商店时,你应该致电
requests.cancel()
确保您不会在请求中间更改viewController。在我的spriteKit游戏中导致我崩溃,因此将其放入其中以确保其被取消是很好的。
6)你在说这行吗
SKPaymentQueue.default().remove(self)
当您关闭应用程序时,或者在退出商店时可能会出现这种情况。这样可以确保所有交易都从观察者中删除,并且将来不会以登录消息的形式显示。
如果这可以解决您的崩溃问题,请告诉我。
答案 1 :(得分:2)
感谢@ crashoverride777(!!!)在添加(Swift 4)后确实修复了同样的问题:
override func viewDidDisappear(_ animated: Bool) {
SKPaymentQueue.default().remove(self)
}
答案 2 :(得分:-1)
谢谢!,也为我工作!!! 提到的崩溃也发生在我身上。上面的答案是相当完整的,只是最后一条评论解决了崩溃问题。
我在Objective-C中使用的代码:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}