草庐IT

ios - 快速恢复购买时崩溃

coder 2023-09-13 原文

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

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

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 类上,一些突出显示的绿色内容表示 Enqueued from

com.apple.root.default-qos.overcommit(Thread 4)

感谢任何帮助!

最佳答案

你的代码有点乱,让我们看一下

1) 将您的 NSUserDefaults key 和产品 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) 为什么要在委托(delegate)方法中将请求设置为 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)

这应该在您关闭您的应用程序时被调用,或者在您的情况下可能在您退出商店时被调用。这确保所有事务都从观察者中删除,并且将来不会以登录消息的形式出现。

如果这能解决您的崩溃问题,请告诉我。

关于ios - 快速恢复购买时崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36298152/

有关ios - 快速恢复购买时崩溃的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  3. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  7. ruby - 如何以表格格式快速打印 Ruby 哈希值? - 2

    有没有办法快速将表格格式的ruby​​哈希打印到文件中?如:keyAkeyBkeyC...1232343451253474456...其中散列的值是不同大小的数组。还是使用双循环是唯一的方法?谢谢 最佳答案 试试我写的这个gem(在表中打印散列、ruby对象、ActiveRecord对象):http://github.com/arches/table_print 关于ruby-如何以表格格式快速打印Ruby哈希值?,我们在StackOverflow上找到一个类似的问题:

  8. 电脑启动后显示器黑屏怎么办?排查下面4个问题,快速解决 - 2

    电脑启动出现显示器黑屏是一个相当常见的问题。如果您遇到了这个问题,不要惊慌,因为它有很多可能的原因,可以采取一些简单的措施来解决它。在本文中,小编将介绍下面4种常见的电脑启动后显示器黑屏的原因,排查这些原因,快速解决! 演示机型:联想Ideapad700-15ISK-ISE系统版本:Windows10一、显示器问题如果出现电脑启动后显示器黑屏的情况。那么首先您需要检查一下显示器是否正常工作。您可以通过更换另一个显示器或将当前显示器连接到另一台计算机来检查显示器是否存在问题。如果问题仍然存在,那么您可以排除显示器故障的可能性。 二、显卡问题如果您的电脑配备了独立显卡,那么显卡故障也可能是导致电脑

  9. ruby - 在多个线程中引用类方法会导致自动加载循环依赖崩溃 - 2

    代码:threads=[]Thread.abort_on_exception=truebegin#throwexceptionsinthreadssowecanseethemthreadseputs"EXCEPTION:#{e.inspect}"puts"MESSAGE:#{e.message}"end崩溃:.rvm/gems/ruby-2.1.3@req/gems/activesupport-4.1.5/lib/active_support/dependencies.rb:478:inload_missing_constant':自动加载常量MyClass时检测到循环依赖稍加研究后,

  10. ruby - 为 IO::popen 拯救 "command not found" - 2

    当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

随机推荐