2010-11-11 6 views
46

I miei acquisti in-app funzionano. Presento un ModalView con un UIButton "Acquista". Fai clic sul pulsante e l'acquisto in app passa attraverso il processo. Puoi anche farlo più volte di seguito.In acquisto di App si blocca su [[SKPaymentQueue defaultQueue] addPayment: pagamento]

Il problema si verifica se si apre la vista modale, quindi si chiude la vista modale (utilizzando un UITabBarButtonItem), quindi si riapre la vista modale e si tocca il pulsante "Acquista". L'applicazione si blocca e mi appare un NSZombie che legge

*** - [InAppPurchaseManager respondsToSelector:]: messaggio inviato a esempio deallocato 0x1c7ad0

I punti NSZombie alla linea 160 nel file .m . L'ho contrassegnato con commenti.

ho ottenuto il codice originale da questa pagina: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

ho lottato con questo per molti giorni ormai ... tutto l'aiuto sarebbe impressionante.

Ecco la .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 

Ecco la .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 

risposta

113

Il messaggio di errore indica un messaggio viene inviato a un'istanza di deallocato InAppPurchaseManager, che è la classe. E succede dopo aver aperto la vista (creando un'istanza), chiusa la vista (rilasciando un'istanza), quindi riaprendo la vista (creando una seconda istanza). E il problema sta accadendo all'interno della chiamata addPayment:. Ciò indica che il framework ha ancora un handle sulla vecchia istanza rilasciata e sta tentando di inviarlo un messaggio.

ti danno il quadro di una maniglia per il vostro oggetto nel loadStore, quando si chiama

[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 

Non vedo da nessuna parte in cui si rimuove self come osservatore. Gli oggetti che inviano notifiche di solito non mantengono i loro osservatori, poiché così facendo è possibile creare un ciclo di conservazione e/o una perdita di memoria.

Nel codice dealloc è necessario eseguire la pulizia e chiamare removeTransactionObserver:. Questo dovrebbe risolvere il tuo problema.

+10

che è stato ... [[SKPaymentQueue defaultQueue] removeTransactionObserver: self]; – Chris

+0

impressionante, un problema così bizzarro risulta da questo. Grazie – Codezy

+1

Great man you ara un genio –

0

Penso che gli osservatori aggiunti utilizzando addTransactionObserver siano riferimenti apparentemente deboli, non forti, il che spiegherebbe questo. Ho effettuato un semplice test:

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

E ha ottenuto lo stesso crash anche senza chiamare removeTransactionObserver. La soluzione nel mio caso è stato quello di mantenere semplicemente un riferimento forte per l'osservatore:

@property (strong) MyObserver* observer; 
.... 
self.observer = [[MyObserver alloc] init]; 
addTransactionObserver(observer);