2010-11-11 72 views
46

我的應用程序內購買工作。我用「購買」UIButton呈現ModalView。您點擊按鈕,In App Purchase即可完成整個流程。你甚至可以連續做幾次。在應用程序購買[[SKPaymentQueue defaultQueue] addPayment崩潰]

如果打開模態視圖,然後關閉模態視圖(使用UITabBarButtonItem),然後重新打開模態視圖並點擊「購買」按鈕,就會出現問題。該應用程序崩潰,我得到一個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 

回答

113

該錯誤消息表示消息被髮送到的InAppPurchaseManager一個釋放的實例,這是你的類。並且在打開視圖(創建實例),關閉視圖(釋放實例),然後再次打開視圖(創建第二個實例)後發生。問題發生在addPayment:調用中。這表明該框架仍然在舊的已發佈實例上有句柄,並且正在嘗試向其發送消息。

你給框架手柄在loadStore你的對象,當你調用

[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 

我沒有在任何地方看到你刪除self作爲觀察員。發出通知的對象通常不會保留其觀察者,因爲這樣做會造成保留週期和/或內存泄漏。

在您的dealloc代碼中,您需要清理並致電removeTransactionObserver:。這應該可以解決你的問題。

+10

這是它... [SKPaymentQueue defaultQueue] removeTransactionObserver:自我]。 – Chris 2010-11-11 03:24:47

+0

真棒,這樣一個奇怪的問題是由此產生的。謝謝 – Codezy 2011-07-27 03:49:49

+1

偉大的人你ara天才 – 2011-09-08 10:27:06

0

我認爲使用addTransactionObserver添加的觀察者顯然是弱引用 - 不是強引用,這可以解釋這一點。我做了一個簡單的測試:

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

即使沒有調用removeTransactionObserver也得到了同樣的崩潰。 在我的情況的解決方案是簡單地保持很強的參考觀察者:

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