在swift

时间:2016-03-30 01:41:28

标签: ios swift crash in-app-purchase

我的应用内购买恢复功能遇到了一些令人困惑的行为。目前,我将恢复功能链接到一个按钮,当我多次激活它时,它似乎崩溃了。例如,如果我点击它,恢复,导航到另一个视图,然后返回再次点击恢复它将崩溃。

任何人都可以检查我的代码,看看我是否遗漏了一些盯着我看的东西?

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)

感谢任何帮助!

3 个答案:

答案 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];
}