线程/UIActivityIndi​​catorView 与应用内购买

发布于 2024-10-01 23:13:04 字数 12888 浏览 0 评论 0原文

首先我要说的是,我的应用内购买有效。

我已经在活动指示器/线程方面苦苦挣扎了一个多星期了。我在让我的微调器 (UIActivityIndi​​catorView) 在我的 InAppPurchase.m 中正常运行时遇到了真正的问题,

我在许多其他地方使用相同的线程代码,并且工作正常。

IAP 进程的工作方式是否存在导致基本线程出现问题的问题?

现在,旋转器在您点击 buyButton 和出现第一个警报(“您想购买吗?...”)之间旋转,但此后不再旋转。

这是 .m 文件:

// InAppPurchaseManager.m

#import "InAppPurchaseManager.h"

#import "GANTracker.h"

@implementation InAppPurchaseManager

@synthesize productID;
@synthesize productsRequest;

@synthesize closeButton;
@synthesize buyButton;
@synthesize testLabel;
@synthesize pView;
@synthesize spinner;
@synthesize spinnerLabel;


- (void)dealloc {

 [productID release];
 //[productsRequest release];

 [closeButton release];
 [buyButton release];
 [testLabel release];
 [pView release];

 [spinner release];
 [spinnerLabel release];

 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

    [super dealloc];
}


- (void)viewDidLoad {
    [super viewDidLoad];

 NSError *error;
 if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase" withError:&error]) {
  //NSLog(@"No GAN Tracking");
 }

 UIColor *backgroundColor = [UIColor colorWithRed:.6745 green:.1333 blue:.1333 alpha:1];
 pView.backgroundColor = backgroundColor;

 [closeButton release];
 closeButton = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStyleBordered target:self action:@selector(closeButtonAction:)];
 self.navigationItem.leftBarButtonItem = closeButton;

 // create the "Loading..." label
 [spinnerLabel release];
 //CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
 spinnerLabel    = [[UILabel alloc] initWithFrame:CGRectMake(35, 145, 250, 35)];
 [spinnerLabel setText:@"Connecting to App Store... "];
 [spinnerLabel setTextColor:[UIColor whiteColor]];
 [spinnerLabel setBackgroundColor:[UIColor blackColor]];
 [spinnerLabel setTextAlignment:UITextAlignmentRight];
 [self.view addSubview:spinnerLabel];
 spinnerLabel.hidden = YES; 

 // create the spinner
 [spinner release];
 spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
 [spinner setCenter:CGPointMake(55,162)];
 [self.view addSubview:spinner];
 spinner.backgroundColor = [UIColor blackColor];
 spinner.hidesWhenStopped = YES;
 [spinner stopAnimating];

 self.navigationItem.title = @"Credits";

 //[self spinTheSpinner];
 //[self loadStore];
 [NSThread detachNewThreadSelector:@selector(loadStore) toTarget:self withObject:nil];



}

-(void)viewDidAppear:(BOOL)animated {
 [self doneSpinning];
 [self updateButtonStatus:@"ON"];
}


-(void)spinTheSpinner {

 NSLog(@"In App Purchase.m == SpinTheSpiner");
 //NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

 [spinner startAnimating];
 spinnerLabel.hidden=NO; 

 //[self performSelectorOnMainThread:@selector(doneSpinning) withObject:nil waitUntilDone:NO];
 //[pool release]; 
}

-(void)doneSpinning {
 NSLog(@"In App Purchase.m == DoneSpinning");
 spinnerLabel.hidden = YES; 
 [spinner stopAnimating];
}

-(void)closeButtonAction:(id)sender { 
 [self dismissModalViewControllerAnimated:YES];
}


-(void)buyButtonAction:(id)sender {

 if([self canMakePurchases]) {
  [self updateButtonStatus:@"OFF"];
  [self spinTheSpinner];

  //[self performSelectorOnMainThread:@selector(requestInAppPurchaseData) withObject:nil waitUntilDone:NO];
  [NSThread detachNewThreadSelector:@selector(requestInAppPurchaseData) toTarget:self withObject:nil];

 } 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
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Load Store");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];
    // restarts any purchases if they were interrupted last time the app was open
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
 [self doneSpinning];
 [pool release];

}


- (void)requestInAppPurchaseData
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Request In App Purchase Data");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseCreditProductId];

    productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    productsRequest.delegate = self;
    [productsRequest start];

 //[self doneSpinning];
 [pool release];

    // we will release the request object in the delegate callback
}



- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
 NSLog(@"did Receive Response");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    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];
 [self purchaseCredit];
}


//
// call this before making a purchase
//
- (BOOL)canMakePurchases
{
 NSLog(@"Can Make Payments");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];
    return [SKPaymentQueue canMakePayments];
}

//
// kick off the upgrade transaction
//
- (void)purchaseCredit
{
 // REMOVED FOR PRIVACY

}

#pragma -
#pragma Purchase helpers

//
// saves a record of the transaction by storing the receipt to disk
//
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 if ([transaction.payment.productIdentifier isEqualToString:kInAppPurchaseCreditProductId])
    {
        // save the transaction receipt to disk
        [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:@"InAppPurchaseTransactionReceipt" ];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
 [pool release];

}

//
// enable pro features
//
- (void)provideContent:(NSString *)productId
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 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];

    }
 [pool release];

}



//
// removes the transaction from the queue and posts a notification with the transaction result
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    // 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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 [self updateButtonStatus:@"OFF"];
 [self spinTheSpinner];

 [NSThread detachNewThreadSelector:@selector(recordTransaction:) toTarget:self withObject:transaction];
 [NSThread detachNewThreadSelector:@selector(provideContent:) toTarget:self withObject:transaction.payment.productIdentifier];

 //[self recordTransaction:transaction];
    //[self provideContent:transaction.payment.productIdentifier];

 [NSThread detachNewThreadSelector:@selector(threadFinishTransaction:) toTarget:self withObject:transaction];
 //[self finishTransaction:transaction wasSuccessful:YES];

 NSError *error;
 if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_done" withError:&error]) {
  //NSLog(@"No GAN Tracking");
 }

 [self doneSpinning];

}

-(void)threadFinishTransaction:(SKPaymentTransaction *)transaction {
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 [self finishTransaction:transaction wasSuccessful:YES]; 
 [pool release];
}

//
// called when a transaction has been restored and and successfully completed
//
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    [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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    if (transaction.error.code != SKErrorPaymentCancelled)
    {
   // error!
  NSError *error;
  if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_error" withError:&error]) {
   //NSLog(@"No GAN Tracking");
  }
        [self finishTransaction:transaction wasSuccessful:NO];
    }
    else
    {
   // this is fine, the user just cancelled, so don’t notify
  NSError *error;
  if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_cancel" withError:&error]) {
   //NSLog(@"No GAN Tracking");
  }

        [[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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    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

Let me first say, my In App Purchase works.

I have been struggling with the Activity Indicator / Threading for more than a week now. I am having real problems getting my spinner (UIActivityIndicatorView) to play nice within my InAppPurchase.m

I am using the same threading code in many other places and it works fine.

Is there something about how the IAP process works that causes problems with basic threading?

Right now, the spinner spins between the time you tap the buyButton and the first alert appears ("Do you want to buy? ..."), but no spinner after that.

Here is the .m File:

// InAppPurchaseManager.m

#import "InAppPurchaseManager.h"

#import "GANTracker.h"

@implementation InAppPurchaseManager

@synthesize productID;
@synthesize productsRequest;

@synthesize closeButton;
@synthesize buyButton;
@synthesize testLabel;
@synthesize pView;
@synthesize spinner;
@synthesize spinnerLabel;


- (void)dealloc {

 [productID release];
 //[productsRequest release];

 [closeButton release];
 [buyButton release];
 [testLabel release];
 [pView release];

 [spinner release];
 [spinnerLabel release];

 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

    [super dealloc];
}


- (void)viewDidLoad {
    [super viewDidLoad];

 NSError *error;
 if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase" withError:&error]) {
  //NSLog(@"No GAN Tracking");
 }

 UIColor *backgroundColor = [UIColor colorWithRed:.6745 green:.1333 blue:.1333 alpha:1];
 pView.backgroundColor = backgroundColor;

 [closeButton release];
 closeButton = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStyleBordered target:self action:@selector(closeButtonAction:)];
 self.navigationItem.leftBarButtonItem = closeButton;

 // create the "Loading..." label
 [spinnerLabel release];
 //CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
 spinnerLabel    = [[UILabel alloc] initWithFrame:CGRectMake(35, 145, 250, 35)];
 [spinnerLabel setText:@"Connecting to App Store... "];
 [spinnerLabel setTextColor:[UIColor whiteColor]];
 [spinnerLabel setBackgroundColor:[UIColor blackColor]];
 [spinnerLabel setTextAlignment:UITextAlignmentRight];
 [self.view addSubview:spinnerLabel];
 spinnerLabel.hidden = YES; 

 // create the spinner
 [spinner release];
 spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
 [spinner setCenter:CGPointMake(55,162)];
 [self.view addSubview:spinner];
 spinner.backgroundColor = [UIColor blackColor];
 spinner.hidesWhenStopped = YES;
 [spinner stopAnimating];

 self.navigationItem.title = @"Credits";

 //[self spinTheSpinner];
 //[self loadStore];
 [NSThread detachNewThreadSelector:@selector(loadStore) toTarget:self withObject:nil];



}

-(void)viewDidAppear:(BOOL)animated {
 [self doneSpinning];
 [self updateButtonStatus:@"ON"];
}


-(void)spinTheSpinner {

 NSLog(@"In App Purchase.m == SpinTheSpiner");
 //NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

 [spinner startAnimating];
 spinnerLabel.hidden=NO; 

 //[self performSelectorOnMainThread:@selector(doneSpinning) withObject:nil waitUntilDone:NO];
 //[pool release]; 
}

-(void)doneSpinning {
 NSLog(@"In App Purchase.m == DoneSpinning");
 spinnerLabel.hidden = YES; 
 [spinner stopAnimating];
}

-(void)closeButtonAction:(id)sender { 
 [self dismissModalViewControllerAnimated:YES];
}


-(void)buyButtonAction:(id)sender {

 if([self canMakePurchases]) {
  [self updateButtonStatus:@"OFF"];
  [self spinTheSpinner];

  //[self performSelectorOnMainThread:@selector(requestInAppPurchaseData) withObject:nil waitUntilDone:NO];
  [NSThread detachNewThreadSelector:@selector(requestInAppPurchaseData) toTarget:self withObject:nil];

 } 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
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Load Store");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];
    // restarts any purchases if they were interrupted last time the app was open
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
 [self doneSpinning];
 [pool release];

}


- (void)requestInAppPurchaseData
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Request In App Purchase Data");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseCreditProductId];

    productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    productsRequest.delegate = self;
    [productsRequest start];

 //[self doneSpinning];
 [pool release];

    // we will release the request object in the delegate callback
}



- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
 NSLog(@"did Receive Response");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    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];
 [self purchaseCredit];
}


//
// call this before making a purchase
//
- (BOOL)canMakePurchases
{
 NSLog(@"Can Make Payments");
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];
    return [SKPaymentQueue canMakePayments];
}

//
// kick off the upgrade transaction
//
- (void)purchaseCredit
{
 // REMOVED FOR PRIVACY

}

#pragma -
#pragma Purchase helpers

//
// saves a record of the transaction by storing the receipt to disk
//
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 if ([transaction.payment.productIdentifier isEqualToString:kInAppPurchaseCreditProductId])
    {
        // save the transaction receipt to disk
        [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:@"InAppPurchaseTransactionReceipt" ];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
 [pool release];

}

//
// enable pro features
//
- (void)provideContent:(NSString *)productId
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 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];

    }
 [pool release];

}



//
// removes the transaction from the queue and posts a notification with the transaction result
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    // 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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

 [self updateButtonStatus:@"OFF"];
 [self spinTheSpinner];

 [NSThread detachNewThreadSelector:@selector(recordTransaction:) toTarget:self withObject:transaction];
 [NSThread detachNewThreadSelector:@selector(provideContent:) toTarget:self withObject:transaction.payment.productIdentifier];

 //[self recordTransaction:transaction];
    //[self provideContent:transaction.payment.productIdentifier];

 [NSThread detachNewThreadSelector:@selector(threadFinishTransaction:) toTarget:self withObject:transaction];
 //[self finishTransaction:transaction wasSuccessful:YES];

 NSError *error;
 if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_done" withError:&error]) {
  //NSLog(@"No GAN Tracking");
 }

 [self doneSpinning];

}

-(void)threadFinishTransaction:(SKPaymentTransaction *)transaction {
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 [self finishTransaction:transaction wasSuccessful:YES]; 
 [pool release];
}

//
// called when a transaction has been restored and and successfully completed
//
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    [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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    if (transaction.error.code != SKErrorPaymentCancelled)
    {
   // error!
  NSError *error;
  if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_error" withError:&error]) {
   //NSLog(@"No GAN Tracking");
  }
        [self finishTransaction:transaction wasSuccessful:NO];
    }
    else
    {
   // this is fine, the user just cancelled, so don’t notify
  NSError *error;
  if (![[GANTracker sharedTracker] trackPageview:@"/in_app_purchase_cancel" withError:&error]) {
   //NSLog(@"No GAN Tracking");
  }

        [[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
{
 //[NSThread detachNewThreadSelector:@selector(spinTheSpinner) toTarget:self withObject:nil];

    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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

哎呦我呸! 2024-10-08 23:13:04

克里斯,有两件事:-

首先,您为什么选择多线程方法?

这里不需要您生成一个新线程。正如您所知,StoreKit api 是异步的,您毕竟使用了回调和委托。这是特别的,因此它不会阻塞主线程,因此您不必生成新线程。几乎可以肯定它在后台线程上工作 - 但您不需要知道这一点,它已经为您处理了。事实上,这段代码不仅不需要后台线程,而且几乎肯定会经历大量的性能成本,因为产生新线程来完成如此少的工作。 IE。线程启动的时间(可能)会比完成您安排的工作所需的时间更长。

所以,如果你的动机是表现,你就会失望。

其次,您的线程代码或缺少线程代码是一团糟。从好的方面来说,重申一下,这些都不需要,所以没什么大问题。

你说你是

在许多地方使用相同的线程代码
其他地方,效果很好

  • 你运气不好。这给你的印象是这应该有效,但事实上它是完全不安全的。线程确实很困难,如果你想这样做,你可能会比阅读一些相关的苹果文档

线程

并发

我不愿意直接从这些指南中吐出东西,通过我模糊的大脑进行翻译,并试图将其传递为我的我自己的建议,但为了激励您阅读指南,我在您的几行代码中添加了一些注释:-

// you start a new background thread to call -loadStore
[NSThread detachNewThreadSelector:@selector(loadStore) toTarget:self withObject:nil];

// you initialize SKPaymentQueue singleton on the background thread - is this allowed? i dont know. I can't see it documented.

// then you add transactionObserver observer on the background thread - which thread do you want to receive notifications on? 1)Main thread, 2)this (background) thread, 3)unsure. If it's not the background thread this probably isnt a good idea
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

// Update the GUI from the background thread. No joking here - you absolutely can never do this. It's well documented.
[self doneSpinning];

// end of method, background thread exits or not? You tell me. hope we get lucky with those notifications 
[pool release];

所以,我想补充一点,我绝对不是应用程序内购买如何工作的专家,但是我敢打赌它没有什么特别的。如果您摆脱后台线程或以线程安全的方式重新实现它(在我看来,这看起来不值得在这里麻烦),您的活动微调器可能会很好。

Chris, 2 things:-

Firstly, why have you opted for a multi-threaded approach?

Nothing here requires you spawn a new thread. The StoreKit api is asynchronous, as you know, you are afterall using the callbacks and delegates. This is specifically so it won't block the main thread and so you don't have to spawn a new thread. It almost certainly does it's work on a background thread - but you don't need to know that, it is handled for you. Infact, not only does this code not require a background thread, you are almost certainly experiencing a substantial performance cost spawning new threads to do so little work. ie. it will (probably) take longer for the thread to start up than it will to do the work you have scheduled on it.

So, if your motivation was performance, you are going to be disapointed.

Secondly, your threading code, or lack of it, is a mess. On the plus side, just to reiterate, none of it is needed, so no big problem.

You say that you are

using the same threading code in many
other places and it works fine

  • You have been unlucky. This has given you the impression that this should work when infact it is completely unsafe. Threading is really tough, and if you want to do it you could do worse than reading some the related apple docs

Threading

Concurrency

I'm reluctant to just spew stuff straight from these guides, translated thru my foggy brain, and try to pass it of as my own advice, but to try to motivate you to read the guides i've adding some comments to a couple of lines of your code:-

// you start a new background thread to call -loadStore
[NSThread detachNewThreadSelector:@selector(loadStore) toTarget:self withObject:nil];

// you initialize SKPaymentQueue singleton on the background thread - is this allowed? i dont know. I can't see it documented.

// then you add transactionObserver observer on the background thread - which thread do you want to receive notifications on? 1)Main thread, 2)this (background) thread, 3)unsure. If it's not the background thread this probably isnt a good idea
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

// Update the GUI from the background thread. No joking here - you absolutely can never do this. It's well documented.
[self doneSpinning];

// end of method, background thread exits or not? You tell me. hope we get lucky with those notifications 
[pool release];

So, i'd like to add that i am definitely no expert on how In App Purchase works but i would bet there is nothing special about it. Your activity spinner will probably be fine if you get rid of the background thread or re-implement it in a thread safe manner (which in my opinion doesn't look worth the trouble here).

不打扰别人 2024-10-08 23:13:04

我只浏览了您的代码,但我看到至少有两个地方看起来您正在尝试从非主(主)线程的线程调用的方法更新 UI(停止微调器,更新标签文本) 。这是不允许的 - 所有 UI 都必须从主线程更新。

如果您需要从后台线程更新 UI,则需要将调用编组到主线程,可能使用 peformSelectorOnMainThread:withObject:

因此,例如,看起来 loadStore 是从非主线程调用的,并且它调用了 didSpinning,更新用户界面。我会进行以下更改:

- (void)loadStore
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Load Store");

 // restarts any purchases if they were interrupted last time the app was open
 [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

 // instead of calling doneSpinning directly, ensure it runs on the main thread
 [self performSelectorOnMainThread: @selector( doneSpinning) withObject: nil];

 [pool release];
}

I only skimmed your code, but I saw at least two places where it looked like you are attempting to update UI (stopping a spinner, updating label text) from a method which is called from a thread that is not the primary (main) thread. This is not allowed - all UI must be updated from the main thread.

If you need to update UI from a background thread you need to marshall the call to the main thread, perhaps using peformSelectorOnMainThread:withObject:

So, for example, it looks like loadStore is called from a non-main thread, and it calls doneSpinning, which updates UI. I'd make the following change:

- (void)loadStore
{
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Load Store");

 // restarts any purchases if they were interrupted last time the app was open
 [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

 // instead of calling doneSpinning directly, ensure it runs on the main thread
 [self performSelectorOnMainThread: @selector( doneSpinning) withObject: nil];

 [pool release];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文