La mejor práctica para agregar un observador de la cola de transacciones en el lanzamiento de la aplicación para Storekit

Tengo problemas para tratar de seguir la recomendación de Apple de agregar un observador de la cola de transacciones endidFinishLaunchingWithOptions.

specíficamente, estoy tratando de adaptar el código de un tutorial de Ray Wenderlich que no hace esto: agrega al observador solo una vez que se toca el botón 'comprar'.

i aplicación se bloquea cuando labuyProducta función @ se llama:

public func buyProduct(_ product: SKProduct) {
    print("Buying \(product.productIdentifier)...")
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
}

En mi registro puedo ver que elinit de IAPHelper se llama dos veces, por lo que se llama aSKPaymentQueue.default().add(self) dos veces. Estoy seguro de que ese es el problema, pero estoy confundido sobre cómo solucionarlo.

Aquí está mi código ...

AppDelegate:

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 (solo código relevante):

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

Respuestas a la pregunta(1)

Su respuesta a la pregunta