title: StoreKit In App Purchases seem to be crashing on specific iPhone models?
body: <p>I'm trying to identify a problem that seems to be happening on iPhone 6's but not iPhone 7's. Whenever one of my iPhone 6 users taps on the button to load my in app purchase data and display the actual in app purchase, it crashes for them. This doesn't seem to be the problem for people running iPhone 7s. I can't tell if the problem is per device model or something else</p>
  
  <p>I have a function called <code>constructPurchasePanel()</code> which brings up a panel displaying my in app purchases. The title of my in app purchases is grabbed from an array of products I have stored in my Menu Scene (which is a singleton)</p>
  
  <p><code>constructPurchasePanel()</code> code</p>
  
  <pre><code>nameLabel.text = SceneCoordinator.shared.menuScene?.products[2].localizedTitle
  nameLabel2.text = SceneCoordinator.shared.menuScene?.products[1].localizedTitle
  nameLabel3.text = SceneCoordinator.shared.menuScene?.products[0].localizedTitle
  </code></pre>
  
  <p>MenuScene products array initialization</p>
  
  <pre><code>var products = [SKProduct]() 
  
  MyProducts.store.requestProducts{success, products in
      if success {
         self.products = products!
      }
  }
  </code></pre>
  
  <p>requestProducts() function, and my other IAP related functions. 99% of this is from Ray Wenderlich's tutorial:</p>
  
  <pre><code>/*
   * Copyright (c) 2016 Razeware LLC
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  import StoreKit
  
  public typealias ProductIdentifier = String
  public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -&gt; ()
  
  open class IAPHelper : NSObject  {
  
      static let IAPHelperPurchaseNotification = "IAPHelperPurchaseNotification"
      fileprivate let productIdentifiers: Set&lt;ProductIdentifier&gt;
      fileprivate var purchasedProductIdentifiers = Set&lt;ProductIdentifier&gt;()
      fileprivate var productsRequest: SKProductsRequest?
      fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
  
      public init(productIds: Set&lt;ProductIdentifier&gt;) {
          productIdentifiers = productIds
          for productIdentifier in productIds {
              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) -&gt; Bool {
          return purchasedProductIdentifiers.contains(productIdentifier)
      }
  
      public class func canMakePayments() -&gt; Bool {
          return SKPaymentQueue.canMakePayments()
      }
  
      public func restorePurchases() {
          SKPaymentQueue.default().restoreCompletedTransactions()
      }
  }
  
  // MARK: - SKProductsRequestDelegate
  
  extension IAPHelper: SKProductsRequestDelegate {
  
      public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
          let products = response.products
          print("Loaded list of 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)
          clearRequestAndHandler()
      }
  
      private 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:
                  complete(transaction: transaction)
                  break
              case .failed:
                  fail(transaction: transaction)
                  break
              case .restored:
                  restore(transaction: transaction)
                  break
              case .deferred:
                  break
              case .purchasing:
                  break
              }
          }
      }
  
      private func applyEffect(productIdentifier: String) {
          switch(productIdentifier) {
          case StackyChefProducts.smallCoinPackage:
              SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
              Chef.sharedInstance.money += 30000
              SceneCoordinator.shared.shopScene?.updateBalance()
              for item in SceneCoordinator.shared.shopScene!.endlessItems { item.updateUI() }
              for item in SceneCoordinator.shared.shopScene!.upgradeItems { item.updateUI() }
          case StackyChefProducts.mediumCoinPackage:
              SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
              Chef.sharedInstance.money += 90000
              SceneCoordinator.shared.shopScene?.updateBalance()
              for item in SceneCoordinator.shared.shopScene!.endlessItems { item.updateUI() }
              for item in SceneCoordinator.shared.shopScene!.upgradeItems { item.updateUI() }
          case StackyChefProducts.largeCoinPackage:
              SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
              Chef.sharedInstance.money += 150000
              SceneCoordinator.shared.shopScene?.updateBalance()
              for item in SceneCoordinator.shared.shopScene!.endlessItems { item.updateUI() }
              for item in SceneCoordinator.shared.shopScene!.upgradeItems { item.updateUI() }
          case StackyChefProducts.removeAdsProduct:
              if GameData.sharedInstance.adsOn {
                  SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
                  GameData.sharedInstance.adsOn = false
                  MenuScene.removeAds.removeFromParent()
                  MenuScene.bannerView.removeFromSuperview()
              }
          case StackyChefProducts.couponProduct:
              SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
              GameData.sharedInstance.coupons += 1
              SceneCoordinator.shared.grabBagScene?.updateCouponButton()
              SceneCoordinator.shared.grabBagScene?.updateIAPButtons()
          case StackyChefProducts.couponsProduct:
              SKTAudio.sharedInstance.playSoundEffect("Content/purchase.wav")
  
              GameData.sharedInstance.coupons += 3
              SceneCoordinator.shared.grabBagScene?.updateCouponButton()
              SceneCoordinator.shared.grabBagScene?.updateIAPButtons()
          default:
              print("applyEffect() shouldn't reach this")
          }
      }
  
      private func complete(transaction: SKPaymentTransaction) {
          print("complete...")
          deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
          SKPaymentQueue.default().finishTransaction(transaction)
  
          applyEffect(productIdentifier: transaction.payment.productIdentifier)
      }
  
      private func restore(transaction: SKPaymentTransaction) {
          guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
  
          print("restore... \(productIdentifier)")
          deliverPurchaseNotificationFor(identifier: productIdentifier)
          SKPaymentQueue.default().finishTransaction(transaction)
  
          applyEffect(productIdentifier: transaction.payment.productIdentifier)
      }
  
      private func fail(transaction: SKPaymentTransaction) {
          print("fail...")
          if let transactionError = transaction.error as NSError? {
              if transactionError.code != SKErrorCode.paymentCancelled.rawValue {
                  print("Transaction Error: \(String(describing: transaction.error?.localizedDescription))")
              }
          }
  
          SKPaymentQueue.default().finishTransaction(transaction)
      }
  
      private func deliverPurchaseNotificationFor(identifier: String?) {
          guard let identifier = identifier else { return }
  
          purchasedProductIdentifiers.insert(identifier)
          UserDefaults.standard.set(true, forKey: identifier)
          UserDefaults.standard.synchronize()
          NotificationCenter.default.post(name: NSNotification.Name(rawValue: IAPHelper.IAPHelperPurchaseNotification), object: identifier)
      }
  }
  </code></pre>
  
  <p>Does anyone have an idea of what could be causing this? What I think is happening is that <code>constructPurchasePanel()</code> gets called, and products array is empty, therefore causing the crash when I try to grab a title from one of the products. This mean something must be going wrong in <code>requestProducts()</code> I believe.</p>
  
  <p>I know the products exist because this code works fine for me and others running iPhone 7s, and I can purchase an IAP with no trouble</p>
  
  <p><a href="https://i.stack.imgur.com/3h1eO.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/3h1eO.png" alt="enter image description here"></a></p>
  
  <p>EDIT:</p>
  
  <p>After talking to some more users it doesn't seem like this is necessarily a device specific thing, so I'm not sure how it's crashing for some but working for others.</p>