App is crashing after an IAP - only after podfiles were updated

纵然是瞬间 提交于 2020-01-03 16:57:29

问题


I have an IAP setup in an app, along with a few Cocoapods:

  - Firebase/AdMob (4.8.0):
    - Firebase/Core
    - Google-Mobile-Ads-SDK (= 7.27.0)
  - Firebase/Core (4.8.0):
    - FirebaseAnalytics (= 4.0.5)
    - FirebaseCore (= 4.0.13)
  - Firebase/Crash (4.8.0):
    - Firebase/Core
    - FirebaseCrash (= 2.0.2)
  - FirebaseAnalytics (4.0.5):
    - FirebaseCore (~> 4.0)
    - FirebaseInstanceID (~> 2.0)
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
    - nanopb (~> 0.3)
  - FirebaseCore (4.0.13):
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
  - FirebaseCrash (2.0.2):
    - FirebaseAnalytics (~> 4.0)
    - FirebaseInstanceID (~> 2.0)
    - GoogleToolboxForMac/Logger (~> 2.1)
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
    - Protobuf (~> 3.1)

IAP and all of the above frameworks are working perfect! No problems at all.

Once I do a pod update, things start to go south.

After a pod update, here are the updated versions:

PODS:

  - Firebase/AdMob (4.10.1):
    - Firebase/Core
    - Google-Mobile-Ads-SDK (= 7.29.0)
  - Firebase/Core (4.10.1):
    - FirebaseAnalytics (= 4.1.0)
    - FirebaseCore (= 4.0.17)
  - Firebase/Crash (4.10.1):
    - Firebase/Core
    - FirebaseCrash (= 2.0.2)
  - FirebaseAnalytics (4.1.0):
    - FirebaseCore (~> 4.0)
    - FirebaseInstanceID (~> 2.0)
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
    - nanopb (~> 0.3)
  - FirebaseCore (4.0.17):
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
  - FirebaseCrash (2.0.2):
    - FirebaseAnalytics (~> 4.0)
    - FirebaseInstanceID (~> 2.0)
    - GoogleToolboxForMac/Logger (~> 2.1)
    - GoogleToolboxForMac/NSData+zlib (~> 2.1)
    - Protobuf (~> 3.1)

After this pod update - my IAP crashes on a successful purchase 100% of the time. Absolutely nothing changed in code. Just a pod update to the newest frameworks listed above.

I am getting the following crash once the IAP completes (and the "You're all set!" success alert pops up):

libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

    libsystem_kernel.dylib`__pthread_kill:
    0x1859bc2e0 <+0>:  mov    x16, #0x148
    0x1859bc2e4 <+4>:  svc    #0x80
->  0x1859bc2e8 <+8>:  b.lo   0x1859bc300               ; <+32>
    0x1859bc2ec <+12>: stp    x29, x30, [sp, #-0x10]!
    0x1859bc2f0 <+16>: mov    x29, sp
    0x1859bc2f4 <+20>: bl     0x18599cbdc               ; cerror_nocancel
    0x1859bc2f8 <+24>: mov    sp, x29
    0x1859bc2fc <+28>: ldp    x29, x30, [sp], #0x10
    0x1859bc300 <+32>: ret

Here is a screenshot of the debug panel: https://i.stack.imgur.com/exmsO.png

[![Debug panel][1]][1]

Here is what Firebase crash reporting is logging:

-[__NSCFBoolean timeIntervalSince1970]: unrecognized selector sent to instance 0x1b6f8a878

Some items to note:

  • Nothing at all changed in code.
  • Cocoapods were updated in terminal. Steps: 1. CD to dir, 2. $ pod update
  • I tested the IAP before updating Cocoapods - everything worked flawless; the app did not crash.
  • I did a project clean before testing the IAP after Cocoapod update.
  • Crashing on multiple devices - (iOS 11.2.6 and 11.2.1).

What is causing this crash since I only updated pods?

Bounty update:

I have added a Bounty to this question because I am now experiencing it on other projects. I had an old project that i wanted to update the Pods (Firebase / Firebase Crash / Google Ads). Here are the exact steps I took:

  1. CD to project directory.
  2. Pod update. Cocoapods did not give me any errors at all.

Within Xcode, I run the project whose Podfile I updated... I go through purchasing an IAP and it crashes as soon as it's complete. Again, this does not happen before the pod file was updated! The IAP works fine until I run pod update.

With the newly offended broken project, I removed Podfile, Podfile.lock, and Pods directory. I dragged the same files and directory in from an older project. Works perfect without any crashing.

This problem is persisting ONLY after pod update. I'm lost..

IAP Helper file

  import StoreKit
import Firebase

public typealias MYProductIdentifier = String
public typealias MYProductRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> ()

// MARK: - Class

public class IAPHelper: NSObject {

    // Define properties!
    fileprivate let myProductIdentifiers: Set<MYProductIdentifier>
    fileprivate var myPurchasedProductIdentifiers = Set<MYProductIdentifier>()

    // Optional properties
    fileprivate var myProductsRequest: SKProductsRequest?
    fileprivate var myProductsRequestCompletionHandler: MYProductRequestCompletionHandler?


    // NOTIFICATION
    static let IAPTransactionInProgress = "IAPTransactionInProgress"
    static let IAPTransactionFailed = "IAPTransactionFailed"
    static let myIAPHelperPurchaseNotification = "IAPHelperPurchaseNotification" // Whenever a purchase takes place!
    static let myRestorePurchaseNotification = "myRestorePurchaseNotification" // Whenever a restore takes place!
    static let myPurchaseMadeThankYou = "myPurchaseMadeThankYou" // Whenever a first purchase takes place!


    // init!
    public init(productIDs: Set<MYProductIdentifier>) {
        myProductIdentifiers = productIDs

        // CHECK IF USER ALREADY BOUGHT! (to set the correct Defaults)
        for productIdentifier in productIDs {
            let purchased = MYConstants.nsDefaults.bool(forKey: productIdentifier)
            if purchased {
                myPurchasedProductIdentifiers.insert(productIdentifier)
                print("Already purchased! \(productIdentifier)")
            }
            else {
                print("Not yet purchased! \(productIdentifier)")
            }
        }

        super.init()

        SKPaymentQueue.default().add(self)

    }


    public func requestProducts(completionHandler: @escaping MYProductRequestCompletionHandler) {
        myProductsRequest?.cancel()
        myProductsRequestCompletionHandler = completionHandler

        myProductsRequest = SKProductsRequest(productIdentifiers: myProductIdentifiers)
        myProductsRequest?.delegate = self
        myProductsRequest?.start()
    }

    public func buyProduct(product: SKProduct) {
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }

    public func isProductPurchased(productIdentifier: MYProductIdentifier) -> Bool {
        return myPurchasedProductIdentifiers.contains(productIdentifier)
    }

    public class func canMakePayment() -> Bool {
        return SKPaymentQueue.canMakePayments()
    }

    public func restorePurchases() {
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

}

// MARK: - SKProductRequestsDelegate

extension IAPHelper: SKProductsRequestDelegate {

    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let products = response.products
        myProductsRequestCompletionHandler?(true, products)
        reset()
    }

    public func request(_ request: SKRequest, didFailWithError error: Error) {
        // Called wheneever there is an ERROR or NO PRODUCTS!
        myProductsRequestCompletionHandler?(false, nil)
        reset()
        print("ERROR \(error.localizedDescription)")
    }

    private func reset() {
        myProductsRequest = nil
        myProductsRequestCompletionHandler = nil
    }

}

// MARK: - SKPaymentTransactionObserver

extension IAPHelper: SKPaymentTransactionObserver {

    // Tells us if the payment from the user was successful. Then react accordingly!

    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        // Check outstanding transactions and react to them.
        for transaction in transactions {
            // check what kind of transaction is happening!
            switch transaction.transactionState {
            case .purchased :
                completeTransaction(transaction: transaction)
            case .failed :
                failedTransaction(transaction: transaction)
            case .restored :
                restoreTransaction(transaction: transaction)
            case .deferred :
                showTransactionAsInProgress(deferred: true)
            case .purchasing :
                showTransactionAsInProgress(deferred: false)
            }
        }

    }

    //MARK: Payment transaction related methods
    private func showTransactionAsInProgress(deferred: Bool) {
        NotificationCenter.default.post(name: Notification.Name(IAPHelper.IAPTransactionInProgress), object: deferred)
    }


    private func completeTransaction(transaction: SKPaymentTransaction) {
        postPurchaseNotificationForIdentifier(identifier: transaction.payment.productIdentifier)
        NotificationCenter.default.post(name: NSNotification.Name(IAPHelper.myPurchaseMadeThankYou), object: nil)
        SKPaymentQueue.default().finishTransaction(transaction)
    }

    private func failedTransaction(transaction: SKPaymentTransaction) {
        // User aborts payment!!
        if transaction.error!._code != SKError.Code.paymentCancelled.rawValue {
            print("Error: \(transaction.error!.localizedDescription)")
        }

        NotificationCenter.default.post(name: Notification.Name(IAPHelper.IAPTransactionFailed), object: transaction.error)

        SKPaymentQueue.default().finishTransaction(transaction)
    }

    private func restoreTransaction(transaction: SKPaymentTransaction) {
        guard let productIdentifier = transaction.original?.payment.productIdentifier else {
            return
        }

        postRestoreNotificationForIdentifier(identifier: productIdentifier)


        SKPaymentQueue.default().finishTransaction(transaction)
    }





    private func postPurchaseNotificationForIdentifier(identifier: String?) {
        // TELL VC THAT PURCHASE WAS OR WAS NOT success.
        guard let identifier = identifier else {
            return
        }

        Analytics.logEvent("IAP_Purchase_Made", parameters: nil)


        // I believe it crashes right here.

        // NEW ==================================
        myPurchasedProductIdentifiers.insert(identifier)
        MYConstants.nsDefaults.set(true, forKey: identifier)
        MYConstants.unlockLogic(restoring: false)
        NotificationCenter.default.post(name: Notification.Name(IAPHelper.myIAPHelperPurchaseNotification), object: identifier)
        // END NEW ==============================

    }



    private func postRestoreNotificationForIdentifier(identifier: String?) {
        // TELL VC THAT PURCHASE WAS OR WAS NOT success.
        guard let identifier = identifier else {
            return
        }

        Analytics.logEvent("IAP_Restore_Made", parameters: nil)

        // NEW ==================================
        myPurchasedProductIdentifiers.insert(identifier)
        MYConstants.nsDefaults.set(true, forKey: identifier)
        print("NEW RESTORE Identifier: \(identifier)")
        MYConstants.unlockLogic(restoring: true)
        NotificationCenter.default.post(name: NSNotification.Name(IAPHelper.myRestorePurchaseNotification), object: nil)
        // END NEW ==============================

    }



}

回答1:


I found out that this crash occurs when registerDefaults: is used to register a user preference key that is the same as the IAP product identifier.

This exception always occurs when the NSUserDefaults key that is the same as the IAP product ID key is has a default prefs value registered like so:

#define kTipPurchasedIAPProductIdKey @"tipAdditional99Cents"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Override point for customization after application launch.


  if (YES) {
      //Firebase initialization
      [FIRApp configure];
      [[FIRConfiguration sharedInstance] setLoggerLevel:FIRLoggerLevelMin];
  }

    //Register user defaults
    [[NSUserDefaults standardUserDefaults] registerDefaults:@{
                                                              kTipPurchasedIAPProductIdKey: @NO
                                                              }];
}

In this case the IAP product ID key (that you would use to validate whether the iAP exists, etc) is tipAdditional99Cents. The crash occurs when the transaction returns as purchased/restored.

The current workaround is to register a different key in the user defaults instead of the actual IAP product ID.

I reported it to Google Firebase on their Github repository here and they will fix the bug in the next release >6.12.0.




回答2:


To find where is this call for the TimeInterval being made I would add a public extension for Bool so you can add a breakpoint to it and modify accordingly.

Something like this:

public extension Bool {
    public var timeIntervalSince1970:TimeInterval {
        get {
            // Add breakpoint here
            return 0
        }
    }
}

Update:

I failed to recognise your crash mentiones NSCFBoolean and not Bool. As I understand NSCFBoolean is a private class bridging CFBoolean, so you can't extend it, but maybe it works by extending CFBoolean. More on NSCFBoolean: https://nshipster.com/bool/

Please, try adding this extension and breakpoint:

public extension CFBoolean {
    var timeIntervalSince1970: TimeInterval {
        get {
            // Add breakpoint here
            return 0
        }
    }
}



回答3:


I've temporarily fixed this issue by going back to the pod versions before I ran the update.

Here are the exact steps I took:

  1. In the Podfile, I commented out the 3 pods, saved & closed.
  2. I then ran pod install to delete the 3 pods.
  3. I then ran pod deintegrate.
  4. I then manually deleted Podfile.lock and Workspace from the project directory.
  5. I re-opened the Podfile, uncommented out the 3 pods, and then explicitly specified the dependency versions that I used before the pod update that was causing the crash. See below:

    pod 'Firebase/Core', '4.8.0'

    pod 'Google-Mobile-Ads-SDK', '7.27.0'

    pod 'Firebase/Crash', '4.8.0'

The project works as intended now. The App completes the IAP with no exceptions.

Why is this occurring? I am led to believe it's NOT a code issue, because this issue only occurs when I update my pods.



来源:https://stackoverflow.com/questions/49325501/app-is-crashing-after-an-iap-only-after-podfiles-were-updated

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!