2010-11-11 13 views
46

Mes achats In-App fonctionnent. Je présente un ModalView avec un UIButton "Buy". Vous cliquez sur le bouton et l'achat dans l'application passe par le processus. Vous pouvez même le faire plusieurs fois de suite.Dans les pannes d'achat d'application sur [[SKPaymentQueue defaultQueue] addPayment: paiement]

Le problème se produit si vous ouvrez la vue modale, puis fermez la vue modale (en utilisant un UITabBarButtonItem), puis rouvrez la vue modale et appuyez sur le bouton "Acheter". Les accidents d'application et je reçois un NSZombie qui lit

*** - [InAppPurchaseManager respondsToSelector]: message envoyé à exemple désallouées 0x1c7ad0

Les points NSZombie à la ligne 160 dans le fichier .m . Je l'ai marqué avec des commentaires.

J'ai obtenu le code original de cette page: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

Je me bats avec cela pendant plusieurs jours maintenant ... toute aide serait génial.

Voici le .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 

Voici le .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 

Répondre

113

Le message d'erreur indique un message est envoyé à une instance désallouées de InAppPurchaseManager, qui est votre classe. Et cela se produit après l'ouverture de la vue (création d'une instance), la fermeture de la vue (libération d'une instance), puis l'ouverture de la vue (création d'une seconde instance). Et le problème se passe dans l'appel addPayment:. Cela indique que le framework a toujours un handle sur votre ancienne instance libérée et essaie de lui envoyer un message.

vous donner le cadre d'une poignée à votre objet dans loadStore, lorsque vous appelez

[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 

Je ne vois nulle part où vous supprimez self à titre d'observateur. Les objets qui envoient des notifications ne conservent généralement pas leurs observateurs, car cela peut créer un cycle de rétention et/ou une fuite de mémoire. Dans votre code dealloc, vous devez nettoyer et appeler le removeTransactionObserver:. Ceci devrait régler votre problème.

+10

C'était-ce ... [[SKPaymentQueue defaultQueue] removeTransactionObserver: auto]; – Chris

+0

génial, un problème si bizarre résulte de cela. Merci – Codezy

+1

Grand homme vous ara un génie –

0

Je pense que les observateurs ajoutés en utilisant addTransactionObserver sont des références apparemment faibles - pas de fortes, ce qui expliquerait cela. J'ai fait un test simple:

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

Et a même le même plantage sans appeler removeTransactionObserver. La solution dans mon cas était de simplement garder une forte référence à l'observateur:

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