问题
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:
- CD to project directory.
- 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:
- In the Podfile, I commented out the 3 pods, saved & closed.
- I then ran
pod install
to delete the 3 pods. - I then ran
pod deintegrate
. - I then manually deleted
Podfile.lock
andWorkspace
from the project directory. 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