2010-11-11 37 views
46

Việc mua hàng trong ứng dụng của tôi hoạt động. Tôi trình bày một ModalView với một "Mua" UIButton. Bạn bấm vào nút và trong ứng dụng mua đi qua quá trình này. Bạn thậm chí có thể làm điều đó nhiều lần liên tiếp.Khi gặp sự cố khi mua ứng dụng trên [[SKPaymentQueue defaultQueue] addPayment: payment]

Sự cố xảy ra nếu bạn mở Chế độ xem phương thức, sau đó đóng Chế độ xem phương thức (sử dụng UITabBarButtonItem), sau đó mở lại Chế độ xem phương thức và nhấn vào nút "Mua". Các tai nạn ứng dụng và tôi nhận được một NSZombie mà đọc

*** - [InAppPurchaseManager respondsToSelector:]: tin nhắn gửi đến dụ deallocated 0x1c7ad0

Những điểm NSZombie để dòng 160 trong file .m . Tôi đã đánh dấu nó bằng các bình luận.

Tôi đã nhận mã gốc từ trang này: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

Tôi đã đấu tranh với điều này trong nhiều ngày nay ... bất kỳ trợ giúp sẽ là tuyệt vời.

Đây là .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 

Đây là .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 

Trả lời

113

Các thông báo lỗi chỉ ra một thông điệp đã được gửi đến một trường hợp deallocated của InAppPurchaseManager, đó là lớp học của bạn. Và nó xảy ra sau khi bạn mở khung nhìn (tạo một thể hiện), đóng khung nhìn (phát hành một cá thể), sau đó mở lại khung nhìn (tạo ra một cá thể thứ hai). Và sự cố đang diễn ra trong cuộc gọi addPayment:. Điều này cho thấy rằng khung vẫn có một xử lý trên phiên bản cũ, đã được phát hành của bạn và đang cố gắng gửi một thông báo.

Bạn cho khuôn khổ một xử lý cho đối tượng của bạn trong loadStore, khi bạn gọi

[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 

tôi không thấy bất cứ nơi nào bạn loại bỏ self như một người quan sát. Các đối tượng gửi thông báo thường không giữ lại quan sát của họ, vì làm như vậy có thể tạo ra một chu trình giữ lại và/hoặc rò rỉ bộ nhớ.

Trong mã dealloc bạn cần phải dọn dẹp và gọi removeTransactionObserver:. Điều đó sẽ giải quyết vấn đề của bạn.

+10

Đó là nó ... [[SKPaymentQueue defaultQueue] removeTransactionObserver: tự]; – Chris

+0

tuyệt vời, như vậy một vấn đề kỳ lạ kết quả từ này. Cảm ơn – Codezy

+1

Tuyệt vời người đàn ông bạn ara một thiên tài –

0

Tôi nghĩ các nhà quan sát được thêm bằng cách sử dụng addTransactionObserver dường như là các tham chiếu yếu - không phải là các tham chiếu mạnh, điều này sẽ giải thích điều này. Tôi đã thực hiện một thử nghiệm đơn giản:

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

Và gặp phải sự cố tương tự ngay cả khi không gọi removeTransactionObserver. Giải pháp trong trường hợp của tôi là chỉ cần giữ một tham chiếu mạnh mẽ đối với người quan sát:

@property (strong) MyObserver* observer; 
.... 
self.observer = [[MyObserver alloc] init]; 
addTransactionObserver(observer); 
Các vấn đề liên quan