Reportes de fallos extraños en el código de compra en la aplicación

Tengo el siguiente código para agregar la compra dentro de la aplicación a la aplicación. Todo funciona perfectamente en mis dispositivos. Pero cuando Crashlytics compatibilidad con Crashlytics a la aplicación, tengo cientos de informes de Crashlytics todos los días. ¿Por qué? Realmente no puedo entender qué está mal en el código provisto.

 #define kInAppPurchaseManagerProductsFetchedNotification @"kInAppPurchaseManagerProductsFetchedNotification" #define kInAppPurchaseManagerTransactionFailedNotification @"kInAppPurchaseManagerTransactionFailedNotification" #define kInAppPurchaseManagerTransactionSucceededNotification @"kInAppPurchaseManagerTransactionSucceededNotification" #define gFullVersion @"%@.FullVersion" - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { [self loadStore]; } } - (NSString*)getProductId:(NSString*)feature { NSBundle *bundle = [NSBundle mainBundle]; NSDictionary *info = [bundle infoDictionary]; NSString *bundleIdentifier = [info objectForKey: @"CFBundleIdentifier"]; return bundleIdentifier; } - (void)requestProducts:(NSString*)feature { NSSet *productIdentifiers = [NSSet setWithObject:[self getProductId:feature]]; if ([feature isEqualToString:gFullVersion]) { if (productFullVersionRequest) { [productFullVersionRequest release]; productFullVersionRequest = nil; } productFullVersionRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; productFullVersionRequest.delegate = self; [productFullVersionRequest start]; // we will release the request object in the delegate callback } } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { [products addObjectsFromArray:response.products]; for (SKProduct *product in response.products) { if (product && [product.productIdentifier isEqualToString:[self getProductId:gFullVersion]]) { // finally release the reqest we alloc/init'ed in requestComstacktions [productFullVersionRequest release]; productFullVersionRequest = nil; } } for (NSString *invalidProductId in response.invalidProductIdentifiers) { NSLog(@"Invalid product id: %@" , invalidProductId); } [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil]; } // call this method once on startup - (void)loadStore { // restarts any purchases if they were interrupted last time the app was open [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; // get the product description (defined in early sections) [self requestProducts:gFullVersion]; } // call this before making a purchase - (BOOL)canMakePurchases { return [SKPaymentQueue canMakePayments]; } // kick off the upgrade transaction - (void)purchaseProduct:(NSString*)feature { bool ok = false; for (SKProduct *product in products) { if ([product.productIdentifier isEqualToString:[self getProductId:feature]]) { SKPayment *payment = [SKPayment paymentWithProduct:product]; if (payment) { [[SKPaymentQueue defaultQueue] addPayment:payment]; break; } } } } // saves a record of the transaction by storing the receipt to disk - (void)recordTransaction:(SKPaymentTransaction *)transaction { if ([transaction.payment.productIdentifier isEqualToString:[self getProductId:gFullVersion]]) { // save the transaction receipt to disk [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:[self getProductId:gFullVersion]]; [[NSUserDefaults standardUserDefaults] synchronize]; } } // enable pro features - (bool)provideContent:(NSString *)productId { if ([productId isEqualToString:[self getProductId:gFullVersion]]) { // ...provide content here... return true; } return false; } // removes the transaction from the queue and posts a notification with the transaction result - (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful { // remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil]; if (wasSuccessful) { // send out a notification that we've finished the transaction [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo]; } else { // send out a notification for the failed transaction [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo]; } } // called when the transaction was successful - (void)completeTransaction:(SKPaymentTransaction *)transaction { [self recordTransaction:transaction]; bool provided = [self provideContent:transaction.payment.productIdentifier]; [self finishTransaction:transaction wasSuccessful:YES]; } // called when a transaction has been restnetworking and and successfully completed - (void)restreTransaction:(SKPaymentTransaction *)transaction { [self recordTransaction:transaction.originalTransaction]; [self provideContent:transaction.originalTransaction.payment.productIdentifier]; [self finishTransaction:transaction wasSuccessful:YES]; } // called when a transaction has failed - (void)failedTransaction:(SKPaymentTransaction *)transaction { if (transaction.error.code != SKErrorPaymentCancelled) { // error! [self finishTransaction:transaction wasSuccessful:NO]; [self showAlert:NSLocalizedString(@"InAppPurchase", @"") alertStr:[transaction.error localizedDescription]]; } else { // this is fine, the user just cancelled, so don't notify [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } // called when the transaction status is updated - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: [self completeTransaction:transaction]; break; case SKPaymentTransactionStateFailed: [self failedTransaction:transaction]; break; case SKPaymentTransactionStateRestonetworking: [self restreTransaction:transaction]; break; default: break; } } } - (IBAction)processFullVersion:(id)sender { if ([self canMakePurchases]) { [self purchaseProduct:gFullVersion]; } else { [self showAlert:NSLocalizedString(@"InAppPurchase", @"") alertStr:NSLocalizedString(@"CanNotMakePurchases", @"")]; } } - (IBAction)restreCompletedTransactions:(id)sender { [[SKPaymentQueue defaultQueue] restreCompletedTransactions]; } 

El informe del crash:

 Crashed: com.apple.main-thread EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0xa1c57ae2 Thread : Crashed: com.apple.main-thread 0 libobjc.A.dylib 0x3bf5e5d0 objc_msgSend + 15 1 StoreKit 0x360260a7 __NotifyObserverAboutChanges + 66 2 CoreFoundation 0x341aeacd CFArrayApplyFunction + 176 3 StoreKit 0x36026055 -[SKPaymentQueue _notifyObserversAboutChanges:sendUpdatedDownloads:] + 128 4 StoreKit 0x36024bc9 -[SKPaymentQueue addPayment:] + 464 5 MyApplication 0x000a76d7 -[CalendarView purchaseProduct:] (CalendarView.m:952) 6 MyApplication 0x000a8c17 -[CalendarView processFullVersion:] (CalendarView.m:1107) 7 MyApplication 0x000a361b -[CalendarView clickHandler:] (CalendarView.m:458) 8 MyApplication 0x000a55cf -[CalendarView liteVersionDisplayAlert] (CalendarView.m:671) 9 MyApplication 0x000a5d2f -[CalendarView toggleView:] (CalendarView.m:735) 10 MyApplication 0x000a625f -[CalendarView modeButtonPressed] (CalendarView.m:795) 11 MyApplication 0x000c245f -[ToolbarView modePressed] (ToolbarView.m:304) 12 UIKit 0x36162087 -[UIApplication sendAction:to:from:forEvent:] + 70 13 UIKit 0x3616203b -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 30 14 UIKit 0x36162015 -[UIControl sendAction:to:forEvent:] + 44 15 UIKit 0x361618cb -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 502 16 UIKit 0x36161db9 -[UIControl touchesEnded:withEvent:] + 488 17 UIKit 0x3608a5f9 -[UIWindow _sendTouchesForEvent:] + 524 18 UIKit 0x360778e1 -[UIApplication sendEvent:] + 380 19 UIKit 0x360771ef _UIApplicationHandleEvent + 6198 20 GraphicsServices 0x37d8f5f7 _PurpleEventCallback + 590 21 GraphicsServices 0x37d8f227 PurpleEventCallback + 34 22 CoreFoundation 0x3423d3e7 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 34 23 CoreFoundation 0x3423d38b __CFRunLoopDoSource1 + 138 24 CoreFoundation 0x3423c20f __CFRunLoopRun + 1382 25 CoreFoundation 0x341af23d CFRunLoopRunSpecific + 356 26 CoreFoundation 0x341af0c9 CFRunLoopRunInMode + 104 27 GraphicsServices 0x37d8e33b GSEventRunModal + 74 28 UIKit 0x360cb2b9 UIApplicationMain + 1120 29 MyApplication 0x0007daff main (main.m:13) 

¡Muchas gracias por la ayuda!

Parece que un observador que agregó a la queue de pago se ha desasignado. SKPaymentQueue addTransactionObserver: llamadas a SKPaymentQueue addTransactionObserver: pero nunca llama a SKPaymentQueue removeTransactionObserver:

Agregue un método dealloc a su controller de vista:

 - (void)dealloc { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } 

Lo más probable es que su aplicación mostrara un controller de vista que se agrega como un observador. Entonces ese controller de vista fue descartado (pero aún marcado como un observador). Más tarde, muestra otra instancia del controller y realiza alguna transacción de pago. La queue de pagos intenta notificar a los observadores antiguos y nuevos. El crash ocurre porque el viejo observador ha sido desasignado hace mucho time.

Al agregar el método dealloc anterior, este problema se resolverá.

Parece que estás usando la observación de valores key. Supongo que cuando la aplicación ejecuta esta sección

 - (void)purchaseProduct:(NSString*)feature { bool ok = false; for (SKProduct *product in products) { if ([product.productIdentifier isEqualToString:[self getProductId:feature]]) { SKPayment *payment = [SKPayment paymentWithProduct:product]; if (payment) { [[SKPaymentQueue defaultQueue] addPayment:payment]; break; } } } } 

Y agrega con éxito un pago que va a actualizar a un observador que no está allí, de ahí el mal acceso a la memory.

Verificaré [[SKPaymentQueue defaultQueue] addTransactionObserver: self];