2016-03-30 65 views
2

我遇到了一些令人困惑的行爲,這些行爲與我的應用內購買還原功能有關。目前,我已將恢復功能鏈接到一個按鈕,當我多次激活它時,它似乎會崩潰。例如,如果我點擊它,恢復,導航到另一個視圖,然後再次點擊恢復,它會崩潰。迅速恢復購買時發生崩潰

任何人都可以檢查我的代碼,看看我是否錯過了盯着我的臉?

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") 
     } 
    } 
} 

}

當它崩潰,沒有太多的信息,我可以破譯。我得到一個錯誤,說明我的AppDelegate類EXC_BAD_ACCESS(代碼= 1,地址= 0xc),並突出顯示綠色,說明入隊從com.apple.root.default-qos.overcommit(線程4)

任何幫助表示讚賞!

+0

什麼是印刷在控制檯中? – nhgrif

+0

這些是最後的意見:'恢復綠色 恢復紅色 恢復藍 (lldb)' – tbaldw02

回答

3

你的代碼是有點亂,讓我們通過它去

1)把你的鑰匙NSUserDefaults的和產品標識爲結構類上面,所以你避免錯別字。

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)爲什麼你在委託方法中將請求設置爲零?這是沒有意義的。刪除所有這些行代碼中的

self.request = nil 

4)您還應該使用originalTransaction在.Restore情況下,你的方式是不太正確的。不幸的是,大量的教程不會教你這個。

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) 

這應該被調用,當你關閉你的應用程序或在您的情況可能是,當你離開商店。這確保所有交易從觀察者中移除,並且將來不會以登錄消息的形式出現。

讓我知道這是否修復你的崩潰。

+0

我不是原來的海報,但只是想說這對我有用。試圖恢復兩次IAP時,我遇到了崩潰。這不是一個正常的工作流程,但它是一個崩潰。我將removeTransactionObserver代碼添加到了View的ViewWillDisappear函數中,並且崩潰消失了。 –

+0

太棒了。是的,這個觀察者很重要,我想。如果您檢查堆棧溢出,那麼在購買/恢復時會遇到一些問題,即人們崩潰。我在開始 – crashoverride777

-1

謝謝!,也爲我工作! 提到的崩潰也發生在我身上。上面的答案非常完整,然而,只有最後一條評論才解決了崩潰問題。

我在Objective-C中使用的代碼:

- (void) viewWillDisappear:(BOOL)animated 
{ 
    [super viewWillDisappear:YES]; 
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; 
} 
1

感謝@ crashoverride777(!!!)完全一樣的問題被加入(SWIFT 4)固定後:

override func viewDidDisappear(_ animated: Bool) { 
     SKPaymentQueue.default().remove(self) 
    } 
+0

有同樣的問題很高興它幫助你。快樂的編碼 – crashoverride777