ios - 在 Storekit 的应用程序启动时添加事务队列观察器的最佳实践
问题描述
我在尝试遵循 Apple 关于在didFinishLaunchingWithOptions
.
具体来说,我正在尝试改编 Ray Wenderlich 教程中的代码,该教程没有这样做 - 它仅在点击“购买”按钮后才添加观察者。
buyProduct
调用该函数时,我的应用程序崩溃:
public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
在我的日志中,我可以看到init
IAPHelper 被调用了两次,因此调用SKPaymentQueue.default().add(self)
了两次。我确信这是问题所在,但我很困惑如何解决它。
这是我的代码...
应用委托:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
IAPHelper.sharedInstance = IAPHelper() // creates the singleton for IAPHelper
// other code here //
return true
}
IAPHelper.swift:
import StoreKit
/// Notification that is generated when a product is purchased.
public let IAPHelperPurchaseNotification = "IAPHelperPurchaseNotification"
/// Notification that is generated when a transaction fails.
public let IAPHelperTransactionFailedNotification = "IAPHelperTransactionFailedNotification"
/// Notification that is generated when cannot retrieve IAPs from iTunes.
public let IAPHelperConnectionErrorNotification = "IAPHelperConnectionErrorNotification"
/// Notification that is generated when we need to stop the spinner.
public let IAPHelperStopSpinnerNotification = "IAPHelperStopSpinnerNotification"
/// Product identifiers are unique strings registered on the app store.
public typealias ProductIdentifier = String
/// Completion handler called when products are fetched.
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> ()
open class IAPHelper : NSObject {
/// MARK: - User facing API
fileprivate let productIdentifiers: Set<ProductIdentifier>
fileprivate var purchasedProductIdentifiers = Set<ProductIdentifier>()
fileprivate var productsRequest: SKProductsRequest?
fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
static var sharedInstance = IAPHelper() // singleton
override init() {
// Set up the list of productIdentifiers
let PackOf4000Coins = "com.xxx.xxx.4000Coins"
let PackOf10000Coins = "com.xxx.xxx.10000Coins"
let PackOf30000Coins = "com.xxx.xxx.30000Coins"
let PackOf75000Coins = "com.xxx.xxx.75000Coins"
let PackOf175000Coins = "com.xxx.xxx.175000Coins"
let PackOf750000Coins = "com.xxx.xxx.750000Coins"
let RemoveAds = "com.xxx.xxx.RemoveAds"
let PlayerEditor = "com.xxx.xxx.PlayerEditor"
self.productIdentifiers = [PackOf4000Coins, PackOf10000Coins, PackOf30000Coins, PackOf75000Coins, PackOf175000Coins, PackOf750000Coins, RemoveAds, PlayerEditor]
for productIdentifier in self.productIdentifiers {
let purchased = UserDefaults.standard.bool(forKey: productIdentifier)
if purchased {
purchasedProductIdentifiers.insert(productIdentifier)
print("Previously purchased: \(productIdentifier)")
} else {
print("Not purchased: \(productIdentifier)")
}
}
super.init()
SKPaymentQueue.default().add(self)
}
}
// MARK: - StoreKit API
extension IAPHelper {
public func requestProducts(_ completionHandler: @escaping ProductsRequestCompletionHandler) {
productsRequest?.cancel()
productsRequestCompletionHandler = completionHandler
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool {
return purchasedProductIdentifiers.contains(productIdentifier)
}
public class func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
public func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("Restore queue finished.")
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperStopSpinnerNotification), object: nil)
}
public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
print("Restore queue failed.")
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperConnectionErrorNotification), object: nil)
}
}
// MARK: - SKProductsRequestDelegate
extension IAPHelper: SKProductsRequestDelegate {
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Loaded list of products...")
let products = response.products
productsRequestCompletionHandler?(true, products)
clearRequestAndHandler()
for p in products {
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
print("Failed to load list of products.")
print("Error: \(error.localizedDescription)")
productsRequestCompletionHandler?(false, nil)
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperConnectionErrorNotification), object: nil)
clearRequestAndHandler()
}
fileprivate func clearRequestAndHandler() {
productsRequest = nil
productsRequestCompletionHandler = nil
}
}
// MARK: - SKPaymentTransactionObserver
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
completeTransaction(transaction)
break
case .failed:
failedTransaction(transaction)
break
case .restored:
restoreTransaction(transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
fileprivate func completeTransaction(_ transaction: SKPaymentTransaction) {
print("completeTransaction...")
deliverPurchaseNotificationForIdentifier(
transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
fileprivate func restoreTransaction(_ transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restoreTransaction... \(productIdentifier)")
deliverPurchaseNotificationForIdentifier(productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
fileprivate func failedTransaction(_ transaction: SKPaymentTransaction) {
print("failedTransaction...")
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperStopSpinnerNotification), object: nil)
if transaction.error!._code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(String(describing: transaction.error?.localizedDescription))")
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperTransactionFailedNotification), object: nil)
} else {
print("Transaction Error else statement")
}
SKPaymentQueue.default().finishTransaction(transaction)
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperTransactionFailedNotification), object: nil)
}
fileprivate func deliverPurchaseNotificationForIdentifier(_ identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
UserDefaults.standard.set(true, forKey: identifier)
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperPurchaseNotification), object: identifier)
}
}
GameStoreViewController.swift(仅相关代码):
@objc func tableView(_ tableView: UITableView!, didSelectRowAtIndexPath indexPath: IndexPath!) {
if IAPHelper.canMakePayments() {
activitySpinnerStart()
let product = _coinProducts[(indexPath as NSIndexPath).row]
IAPHelper.sharedInstance.buyProduct(product) // Purchasing the product. Fires productPurchased(notification:)
} else {
showAlertWith(Localization("NoConnectionAlertTitle"), message: Localization("NoIAPAllowedMessage"))
}
}
解决方案
最后我使用了 SwiftyStoreKit为我解决了这个问题。我强烈推荐它。
编辑以显示SwiftyStoreKit.completeTransactions
appDelegate 中didFinishLaunchingWithOptions()
的内容。unlockIAPContent()
是我放置购买逻辑的地方,它将处理促销代码和未完成的交易:
// This registers the transaction observer and listens for unfinished transactions
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
// Unlock content
self.unlockIAPContent(productID: purchase.productId)
if purchase.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
推荐阅读
- reactjs - 如何在 React js 中重新渲染页面以获取其默认值
- c# - C# build 会生成很多内置的 dll 文件
- python - 在Tensorflow中的序列张量中将尾随反转为前导填充零
- postgresql - PostgreSQL Serializable 可以安全地与较低的隔离级别混合使用吗?
- spring-boot - 项目中的 sparql 查询
- batch-file - 选择命令中的所有选项
- python - Linux:来自 os.pipe() 的文件描述符未出现在 /dev/fd 中
- python - 如何通过 Flask 添加/修改 HTML 属性?
- jquery - jQuery Datatable 为我要创建的两个不同按钮显示相同的按钮名称
- python - 如何在python变量中有多个数组