ios - 更改场景时,应用内购买会导致应用崩溃

标签 ios iphone swift xcode in-app-purchase

我已经在我的游戏的商店场景中实现了应用程序购买,并且在从商店场景切换到另一个场景时遇到了问题,它似乎使游戏崩溃并给我这个错误

Thread 1: EXC_BAD_ACCESS (code=1, address=0x840f8010)

或者它给了我其他错误的多个版本,例如:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x3f8)

它有时也会给我一个错误,像这样更改子类中的行

enter image description here

当我注释掉子类中搜索应用程序购买信息或将手机置于飞行模式的代码时,它工作正常并且问题消失了。

我有一个 SKnode 的子类,它获取有关可购买商品的信息,然后通过使用 SKLabels 和 sprite 节点显示它,以显示商店中购买的图片,如下所示:

class InAppPurchaseItems: SKNode, SKProductsRequestDelegate {

var shopItemNode = SKSpriteNode()
var itemPriceBackground = SKSpriteNode()
var shopItemLabel = SKLabelNode()
var shopItemTitleLabel = SKLabelNode()
var pressableNode = SKSpriteNode()
var itemPriceLabel = SKLabelNode()

var title: String = ""
var information: String = ""
var image: String = ""

var price:String = "X"

func createAppPurchaseItem(ID: String, purchaseImage: String, purchaseTitle:String) {

    title = purchaseTitle
    image = purchaseImage

    createTheNode()

    //let product = SKProduct()
    let productID: NSSet = NSSet(objects: ID)  //"RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
    let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
    request.delegate = self as? SKProductsRequestDelegate
    request.start()
}

public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        if product.productIdentifier == "RedShield.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            information = product.localizedDescription

        } else if product.productIdentifier == "DoubleCoin.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            information = product.localizedDescription

        } else if product.productIdentifier == "1500Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirOne")

        } else if product.productIdentifier == "7500Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirTwo")

        } else if product.productIdentifier == "14000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirThree")

        } else if product.productIdentifier == "28000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirFour")

        } else if product.productIdentifier == "65000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirFive")

        } else if product.productIdentifier == "128000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirSix")

        }

         createShopLabels()
    }
}

func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}

func createTheNode() {

    let tex:SKTexture = SKTexture(imageNamed: image)

    shopItemNode = SKSpriteNode(texture: tex, color: SKColor.black, size: CGSize(width: 85, height: 85)) //frame.maxX / 20, height: frame.maxY / 20))
    shopItemNode.zPosition = -10
    shopItemNode.position = CGPoint(x: 0, y: 35)

    self.addChild(shopItemNode)
    self.name = "ShopItem"
    self.zPosition = -11

    shopItemTitleLabel = SKLabelNode(fontNamed: "Avenir-Black")
    shopItemTitleLabel.fontColor = UIColor.black;
    shopItemTitleLabel.fontSize = 15 //self.frame.maxY/30
    shopItemTitleLabel.position = CGPoint (x: 0, y: -30)
    shopItemTitleLabel.text = "\(title)"
    shopItemTitleLabel.zPosition = -9
    self.addChild(shopItemTitleLabel)

    itemPriceBackground = SKSpriteNode(texture: SKTexture(imageNamed: "PriceShopBackground"), color: .clear, size: CGSize(width: 80, height: 30)) //SKSpriteNode(color: SKColor.black, size: CGSize(width: 65, height: 20))
    //itemPriceBackground.alpha = 0.4
    itemPriceBackground.zPosition = -10
    itemPriceBackground.position = CGPoint(x: 0, y: -54)
    addChild(itemPriceBackground)

    pressableNode = SKSpriteNode(texture: nil, color: .clear, size: CGSize(width: 100, height: 140))
    pressableNode.zPosition = -7
    pressableNode.position = CGPoint(x: 0, y: 0)
    shopItemSprites.append(pressableNode)
    addChild(pressableNode)

}

func createShopLabels() {

    shopItemLabel = SKLabelNode(fontNamed: "Avenir-Black")
    shopItemLabel.fontColor = UIColor.white;
    shopItemLabel.fontSize = 15 //self.frame.maxY/30
    shopItemLabel.position = CGPoint (x: 0, y: -60)
    shopItemLabel.text = "\(price)"
    shopItemLabel.zPosition = -9
    addChild(shopItemLabel)

 }

}

然后使用以下代码将它们显示在商店场景中:

 let ShopItem = InAppPurchaseItems()
 ShopItem.createAppPurchaseItem(ID: "DoubleCoin.Astrum.Purchase", purchaseImage: "2StarCoin", purchaseTitle: "+2 In Game Pickups")
 ShopItem.position = CGPoint(x: self.frame.midX / 1.6, y: self.frame.midY * 0.8)
 ShopItem.zPosition = 100
 ShopItem.name = "Shp0"
 moveableArea.addChild(ShopItem)

商店的主要类

商店主类也有用于购买产品的应用内购买代码,也可以像在子类中一样搜索产品信息,如下所示

class ShopItemMenu: SKScene, SKProductsRequestDelegate, SKPaymentTransactionObserver {

//Purchase Variables
var listOfProducts = [SKProduct]()
var p = SKProduct()

override func didMoveToView(to view: SKView) {

 let ShopItem = InAppPurchaseItems()
 ShopItem.createAppPurchaseItem(ID: "DoubleCoin.Astrum.Purchase", purchaseImage: "2StarCoin", purchaseTitle: "+2 In Game Pickups")
 ShopItem.position = CGPoint(x: self.frame.midX / 1.6, y: self.frame.midY * 0.8)
 ShopItem.zPosition = 100
 ShopItem.name = "Shp0"
 moveableArea.addChild(ShopItem)

}


//This function allows for a product to be bought buy the user and starts the proccess for purchasing
func appPurchaseBuying(appPurchaseID:String) {
    for product in listOfProducts {
        let prodID = product.productIdentifier
        if(prodID == appPurchaseID) {
            p = product
            buyProduct()
        }
    }
}

//This Function restores all previously purchased Items (use this for the restor button.
func restorePurchasesOfItems() {
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}


//This function checks if they can make payments and then loads the product ids from a harcoded set. (use this to start when the scene starts)
func checkCanMakePayment() {
    if (SKPaymentQueue.canMakePayments()) {
        print("can make payments...")
        let productID: NSSet = NSSet(objects: "RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
        let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
        request.delegate = self
        request.start()
    } else {
        let alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchases in Settings", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)

            let url: NSURL? = NSURL(string: UIApplicationOpenSettingsURLString)
            if url != nil
            {
                UIApplication.shared.openURL(url! as URL)
            }

        }))
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)
        }))

        if let vc = self.scene?.view?.window?.rootViewController {
            vc.present(alert, animated: true, completion: nil)
        }
    }
}

//This allows the user to buy the product with a product idetifier given by the variable "p"
func buyProduct() {
    print("buying " + p.productIdentifier)
    let pay = SKPayment(product: p)
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().add(pay as SKPayment)
}

//This Function gets all the avaliable products from apple and puts them into the product Array called listOfProducts
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
   /* print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        if product.productIdentifier == "RedShield.Astrum.Purchase" {

            shieldPurchasePrice = priceStringForProduct(item: product)!

        } else if product.productIdentifier == "DoubleCoin.Astrum.Purchase" {

            DoubleCoinPurchasePrice = priceStringForProduct(item: product)!
        }

      /*print(product.productIdentifier)
        print(product.localizedTitle)
        print(product.localizedDescription)
        print(product.price)
     */
        listOfProducts.append(product)
    }*/
}




func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}



    //This Function restores all the already purchased products so that things can be restored such as shield
    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("restoring all...")
    for transaction in queue.transactions {
        let t: SKPaymentTransaction = transaction
        let prodID = t.payment.productIdentifier as String

        switch prodID {
        case "RedShield.Astrum.Purchase":
            isRedShieldPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
            print("finished restoring this purchase")

        case "DoubleCoin.Astrum.Purchase":
            isCoinPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
            print("finished restoring this purchase")

        default:
            print("IAP not found")
        }
    }

    alert(title: "Restored", msg: "Purchases were restored")
}

//This Function is run when the user makes a purchase and checks the state of the purchase to make sure it works
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    print("adding payment...")

    for transaction: AnyObject in transactions {
        let trans = transaction as! SKPaymentTransaction
        print(trans.error)

        switch trans.transactionState {
        case .purchased:
            print("buying ok, Unlocking purchase...")
            print(p.productIdentifier)

            let prodID = p.productIdentifier
            switch prodID {

                case "RedShield.Astrum.Purchase":
                    isRedShieldPurchaseOn = true

                    let defaults = UserDefaults.standard
                    defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
                    print("unlocked Purchase")

                case "DoubleCoin.Astrum.Purchase":
                    isCoinPurchaseOn = true

                    let defaults = UserDefaults.standard
                    defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
                    print("unlocked Purchase")

                case "SOME IN APP PURCHASE ID HERE":
                    print("unlocked Purchase")

                default:
                    print("IAP Not found")
            }

            queue.finishTransaction(trans)

        case .failed:
            print("error with payment...")
            queue.finishTransaction(trans)

        default:
            print("Default")
        }
    }
}

我是否正在以正确的方式执行此操作,或者是否有更好的方法来执行此操作以及如何解决我遇到的崩溃问题?

编辑

enter image description here

编辑 2

enter image description here

编辑 3

enter image description here

编辑 4

import Foundation
import SpriteKit
import StoreKit

class PurchaseService {

static let session = PurchaseService()
var products = [SKProduct]()
var p = SKProduct()



//This function allows for a product to be bought buy the user and starts the proccess for purchasing
func appPurchaseBuying(appPurchaseID:String) {
    for product in products {
        let prodID = product.productIdentifier
        if(prodID == appPurchaseID) {
            p = product
            buyProduct()
        }
    }
}

//This Function restores all previously purchased Items (use this for the restor button.
func restorePurchasesOfItems() {
    //SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}


//This function checks if they can make payments and then loads the product ids from a harcoded set. (use this to start when the scene starts)
func checkCanMakePayment() {
    if (SKPaymentQueue.canMakePayments()) {
        print("can make payments...")
        let productID: NSSet = NSSet(objects: "RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
        let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
        //request.delegate = self
        request.start()
    } else {
        let alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchases in Settings", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)

            let url: NSURL? = NSURL(string: UIApplicationOpenSettingsURLString)
            if url != nil
            {
                UIApplication.shared.openURL(url! as URL)
            }

        }))
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)
        }))
    }
}

//This allows the user to buy the product with a product idetifier given by the variable "p"
func buyProduct() {
    print("buying " + p.productIdentifier)
    let pay = SKPayment(product: p)
    //SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().add(pay as SKPayment)
}

//This Function gets all the avaliable products from apple and puts them into the product Array called listOfProducts
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        products.append(product)
    }
}




func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}



//This Function restores all the already purchased products so that things can be restored such as shield
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("restoring all...")
    for transaction in queue.transactions {
        let t: SKPaymentTransaction = transaction
        let prodID = t.payment.productIdentifier as String

        switch prodID {
        case "RedShield.Astrum.Purchase":
            isRedShieldPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
            print("finished restoring this purchase")

        case "DoubleCoin.Astrum.Purchase":
            isCoinPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
            print("finished restoring this purchase")

        default:
            print("IAP not found")
        }
    }

    //alert(title: "Restored", msg: "Purchases were restored")
}

//This Function is run when the user makes a purchase and checks the state of the purchase to make sure it works
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    print("adding payment...")

    for transaction: AnyObject in transactions {
        let trans = transaction as! SKPaymentTransaction
        print(trans.error)

        switch trans.transactionState {
        case .purchased:
            print("buying ok, Unlocking purchase...")
            print(p.productIdentifier)

            let prodID = p.productIdentifier
            switch prodID {

            case "RedShield.Astrum.Purchase":
                isRedShieldPurchaseOn = true

                let defaults = UserDefaults.standard
                defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
                print("unlocked Purchase")

            case "DoubleCoin.Astrum.Purchase":
                isCoinPurchaseOn = true

                let defaults = UserDefaults.standard
                defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
                print("unlocked Purchase")

            case "SOME IN APP PURCHASE ID HERE":
                print("unlocked Purchase")

            default:
                print("IAP Not found")
            }

            queue.finishTransaction(trans)

        case .failed:
            print("error with payment...")
            queue.finishTransaction(trans)

        default:
            print("Default")
        }
    }
}

}

最佳答案

在您的游戏场景中拥有所有 StoreKit 代码会使您更难隔离您遇到的问题。我建议您创建一个新的 swift 文件,我们将其称为 PurchaseService,并使用如下静态实例:

class PurchaseService {
      static let session = PurchaseService()
      var products = [SKProduct]()
      // code
}

您可以在此处实现所有与采购相关的逻辑。我通常使用 getPurchases 函数从商店加载可用的购买,并从 AppDelegate.swift 文件的应用程序函数调用它。这确保您的购买很早就加载并且在您需要它们的第一时间就准备好(因为您创建了一个静态实例,您可以在需要通过 PurchaseService.session 进行购买时随时引用它...)

要获取价格,您可以使用一个函数来遍历您的产品变量并检查产品 ID:

func price(for productID:String)->Double{
  if products.count>0 {
  for product in products {
    if product.productIdentifier == productID {
      return product.price.doubleValue
    }
  }
 }
}

如果您遵守 SKProductRequestDelegate 协议(protocol),则无需有条件地将 self 转换为它:

 // unnecessary:  request.delegate = self as? SKProductsRequestDelegate
    request.delegate = self

想知道您是否公开了 productRequest 方法,因为在请求返回时,SKProductResponse 对象自身不再可用。

关于您项目中的 Objective-C 代码:我看到您可能正在使用 Firebase(我从您的控制台消息推断)并且它有一些 Objective-C 的零碎部分。

关于ios - 更改场景时,应用内购买会导致应用崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47129841/

相关文章:

ios - 在 UITabbarController 中设置 View Controller 的委托(delegate)

ios - Xcode 9 没有这样的模块 'Facebook Login'

ios - KVO 不工作 : message was received but not handled

ios - UITextField 上的圆角边缘

iphone - "Embedded profile header length is greater than data length."错误是什么意思?

uitableview - UITableViewController 与 Segues、Core Data 和 Swift 的问题

ios - Realm ignoredProperties 不适用于复合主键

ios - "Editor placeholder in source file"CAPBridge.swift 中的 Swift 编译错误

iphone - 获取国家域名代码

swift - 在 Swift 中从 Array of Dictionary of Array 中过滤项目