在应用程序购买中的线程/ UIActivityIndi​​catorView

时间:2010-11-13 03:46:50

标签: iphone in-app-purchase uiactivityindicatorview

首先我要说,我的In App Purchase有效。

我一直在努力使用活动指标/线程超过一周。我在让我的微调器(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

2 个答案:

答案 0 :(得分:2)

克里斯,2件事: -

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

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

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

其次,您的线程代码或缺少它是一团糟。从正面来看,重申一下,没有必要,所以没有大问题。

你说你是

  

使用相同的线程代码   其他地方,它工作正常

  • 你不走运。这给你的印象是,这应该在事实上完全不安全时起作用。线程真的很难,如果你想这样做,你可能会比阅读一些相关的苹果文档更糟糕

Threading

Concurrency

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

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

所以,我想补充一点,我绝对不是In App Purchase如何运作的专家,但我敢打赌它并没有什么特别之处。如果你摆脱后台线程或者以线程安全的方式重新实现它,你的活动微调器可能会没问题(在我看来这看起来不值得这么麻烦)。

答案 1 :(得分:1)

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

如果你需要从后台线程更新UI,你需要编组调用主线程,也许使用peformSelectorOnMainThread:withObject:

因此,例如,看起来像是从非主线程调用loadStore,它调用了更新UI的doneSpinning。我会做出以下改变:

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