In case of application crashes in [[SKPaymentQueue defaultQueue] addPayment: payment]

Work with my applications. I am introducing ModalView using the "Buy" UIButton. You press the button and the “In App Purchase” application goes through the process. You can do this several times in a row.

The problem arises if you open Modal View, then close Modal View (using UITabBarButtonItem), then open Modal View again and click the Buy button. The application crashes and I get an NSZombie that reads

*** - [InAppPurchaseManager responds with SoSelector:]: message sent to deallocated instance 0x1c7ad0

NSZombie points to line 160 in the .m file. I noted with his comments.

I got the source code from this page: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

I have been struggling with this for many days ... any help would be awesome.

Here is the .h

// // InAppPurchaseManager.h // Copyright 2010 __MyCompanyName__. All rights reserved. #import <UIKit/UIKit.h> #import <StoreKit/StoreKit.h> #define kInAppPurchaseManagerProductsFetchedNotification @"kInAppPurchaseManagerProductsFetchedNotification" #define kInAppPurchaseManagerTransactionFailedNotification @"kInAppPurchaseManagerTransactionFailedNotification" #define kInAppPurchaseManagerTransactionSucceededNotification @"kInAppPurchaseManagerTransactionSucceededNotification" #define kInAppPurchaseCreditProductId @"com.myname.app.iap" @interface InAppPurchaseManager : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver> { SKProduct *productID; SKProductsRequest *productsRequest; IBOutlet UIBarButtonItem *closeButton; IBOutlet UIButton *buyButton; IBOutlet UILabel *testLabel; } @property (retain, nonatomic) SKProduct *productID; @property (retain, nonatomic) SKProductsRequest *productsRequest; @property (retain, nonatomic) IBOutlet UIBarButtonItem *closeButton; @property (retain, nonatomic) IBOutlet UIButton *buyButton; @property (retain, nonatomic) IBOutlet UILabel *testLabel; // public methods -(void)loadStore; -(BOOL)canMakePurchases; -(void)purchaseCredit; -(void)requestInAppPurchaseData; -(void)buyButtonAction:(id)sender; -(void)closeButtonAction:(id)sender; -(void)updateButtonStatus:(NSString *)status; @end 

Here ..m

 // InAppPurchaseManager.m #import "InAppPurchaseManager.h" @implementation InAppPurchaseManager @synthesize productID; @synthesize productsRequest; @synthesize closeButton; @synthesize buyButton; @synthesize testLabel; - (void)dealloc { [productID release]; //[productsRequest release]; [closeButton release]; [buyButton release]; [testLabel release]; [super dealloc]; } - (void)viewDidLoad { [super viewDidLoad]; [closeButton release]; closeButton = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStyleBordered target:self action:@selector(closeButtonAction:)]; self.navigationItem.leftBarButtonItem = closeButton; [self loadStore]; self.navigationItem.title = @"Credits"; } -(void)closeButtonAction:(id)sender { [self dismissModalViewControllerAnimated:YES]; } -(void)buyButtonAction:(id)sender { if([self canMakePurchases]) { [self updateButtonStatus:@"OFF"]; [self performSelectorOnMainThread:@selector(requestInAppPurchaseData) withObject:nil waitUntilDone:NO]; } else { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:[NSString stringWithString:@"Your account settings do not allow for In App Purchases."] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; [alertView release]; } } -(void)updateButtonStatus:(NSString *)status { if ([status isEqual:@"OFF"]) { closeButton.enabled = NO; buyButton.enabled = NO; buyButton.titleLabel.textColor = [UIColor grayColor]; } else { closeButton.enabled = YES; buyButton.enabled = YES; buyButton.titleLabel.textColor = [UIColor blueColor]; } } #pragma mark - #pragma mark SKProductsRequestDelegate methods // // 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]; } - (void)requestInAppPurchaseData { NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseCreditProductId]; productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; productsRequest.delegate = self; [productsRequest start]; // we will release the request object in the delegate callback } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray *products = response.products; productID = [products count] == 1 ? [[products objectAtIndex:0] retain] : nil; if (productID) { /* NSLog(@"Product title: %@" , productID.localizedTitle); NSLog(@"Product description: %@" , productID.localizedDescription); NSLog(@"Product price: %@" , productID.price); NSLog(@"Product id: %@" , productID.productIdentifier); */ NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; NSString *currentCredits = ([standardUserDefaults objectForKey:@"currentCredits"]) ? [standardUserDefaults objectForKey:@"currentCredits"] : @"0"; testLabel.text = [NSString stringWithFormat:@"%@", currentCredits]; } for (NSString *invalidProductId in response.invalidProductIdentifiers) { //NSLog(@"Invalid product id: %@" , invalidProductId); testLabel.text = @"Try Again Later."; } // finally release the reqest we alloc/init'ed in requestProUpgradeProductData [productsRequest release]; [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil]; [self performSelectorOnMainThread:@selector(purchaseCredit) withObject:nil waitUntilDone:NO]; } // // call this before making a purchase // - (BOOL)canMakePurchases { return [SKPaymentQueue canMakePayments]; } // // kick off the upgrade transaction // - (void)purchaseCredit { SKPayment *payment = [SKPayment paymentWithProductIdentifier:kInAppPurchaseCreditProductId]; // ********************************************************************************************************* [[SKPaymentQueue defaultQueue] addPayment:payment]; // <--- This is where the NSZombie Appears ************* // ********************************************************************************************************* } #pragma - #pragma Purchase helpers // // saves a record of the transaction by storing the receipt to disk // - (void)recordTransaction:(SKPaymentTransaction *)transaction { if ([transaction.payment.productIdentifier isEqualToString:kInAppPurchaseCreditProductId]) { // save the transaction receipt to disk [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:@"InAppPurchaseTransactionReceipt" ]; [[NSUserDefaults standardUserDefaults] synchronize]; } } // // enable pro features // - (void)provideContent:(NSString *)productId { if ([productId isEqualToString:kInAppPurchaseCreditProductId]) { // Increment currentCredits NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults]; NSString *currentCredits = [standardUserDefaults objectForKey:@"currentCredits"]; int newCreditCount = [currentCredits intValue] + 1; [standardUserDefaults setObject:[NSString stringWithFormat:@"%d", newCreditCount] forKey:@"currentCredits"]; testLabel.text = [NSString stringWithFormat:@"%d", newCreditCount]; } } // // 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]; } [self updateButtonStatus:@"ON"]; } // // called when the transaction was successful // - (void)completeTransaction:(SKPaymentTransaction *)transaction { [self updateButtonStatus:@"OFF"]; [self recordTransaction:transaction]; [self provideContent:transaction.payment.productIdentifier]; [self finishTransaction:transaction wasSuccessful:YES]; } // // called when a transaction has been restored and and successfully completed // - (void)restoreTransaction:(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]; } else { // this is fine, the user just cancelled, so don't notify [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } [self updateButtonStatus:@"ON"]; } #pragma mark - #pragma mark SKPaymentTransactionObserver methods // // 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 SKPaymentTransactionStateRestored: [self restoreTransaction:transaction]; break; default: break; } } } @end 
+44
iphone crash nszombie in-app-purchase
Nov 11 '10 at 2:30
source share
2 answers

The error message indicates that the message is being sent to a freed instance of InAppPurchaseManager , which is your class. And this happens after you open the view (create an instance), close the view (free the instance), and then open the view again (creating the second instance). And the problem occurs in the addPayment: call. This indicates that the structure still has a handle to your old, released instance and is trying to send a message to it.

You give the infrastructure a handle to your object in loadStore when you call

 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 

I don't see anywhere where you delete self as an observer. Notification entities usually do not save their observers, as this can create a save cycle and / or memory leak.

In the dealloc code, you need to do a cleanup and call removeTransactionObserver: This should solve your problem.

+112
Nov 11 '10 at 3:00
source share

I think the watchers added with addTransactionObserver are apparently weak links - not strong ones, which explains this. I did a simple test:

 // bad code below: // the reference is weak so the observer is immediately destroyed addTransactionObserver([[MyObserver alloc] init]); ... [[SKPaymentQueue defaultQueue] addPayment:payment]; // crash 

And got the same crash even without calling removeTransactionObserver. The solution in my case was to just keep a reference to the observer:

 @property (strong) MyObserver* observer; .... self.observer = [[MyObserver alloc] init]; addTransactionObserver(observer); 
0
Dec 01 '16 at 8:24
source share



All Articles