在应用程序购买 addPayment:付款崩溃]
我的应用程序内购买工作。 我用一个“购买”UIButton呈现一个ModalView。 您单击button,In App Purchase将执行此过程。 你甚至可以连续做几次。
如果打开模态视图,然后closures模态视图(使用UITabBarButtonItem),然后重新打开模态视图并点击“购买”button,就会出现问题。 该应用程序崩溃,我得到一个NSZombie读取
*** – [InAppPurchaseManager respondsToSelector:]:发送到释放实例0x1c7ad0的消息
NSZombie指向.m文件中的第160行。 我已经用评论标记了它。
我从这个页面获得了原始代码: http : //troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/
我已经为此奋斗了很多天了…任何帮助将是真棒。
这是.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
这是.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
该错误消息指示消息正在发送到InAppPurchaseManager
的释放实例,该实例是您的类。 在打开视图(创build实例),closures视图(释放实例),然后再次打开视图(创build第二个实例)之后,就会发生这种情况。 问题发生在addPayment:
调用中。 这表明该框架仍然有一个处理你的旧的,发布的实例,并试图发送一条消息。
当你调用的时候,你给了框架一个处理loadStore中的对象的句柄
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
作为观察者,我不会在任何地方看到self
。 发出通知的对象通常不会保留其观察者,因为这样做会造成保留周期和/或内存泄漏。
在你的dealloc
代码中,你需要清理并调用removeTransactionObserver:
这应该可以解决你的问题。
我认为使用addTransactionObserver添加的观察者显然是弱引用 – 不是强壮的,这将解释这一点。 我做了一个简单的testing:
// bad code below: // the reference is weak so the observer is immediately destroyed addTransactionObserver([[MyObserver alloc] init]); ... [[SKPaymentQueue defaultQueue] addPayment:payment]; // crash
即使没有调用removeTransactionObserver也得到了同样的崩溃。 在我的情况下,解决scheme只是保持强烈的参考观察员:
@property (strong) MyObserver* observer; .... self.observer = [[MyObserver alloc] init]; addTransactionObserver(observer);