Threading / UIActivityIndicatorView с покупкой в ​​приложении - PullRequest
2 голосов
/ 13 ноября 2010

Позвольте мне сначала сказать, что моя покупка в приложении работает.

Я борюсь с Индикатором активности / Потоками больше недели. У меня возникают реальные проблемы с тем, чтобы мой спиннер (UIActivityIndicatorView) хорошо играл в моем InAppPurchase.m

Я использую тот же код многопоточности во многих других местах, и он отлично работает.

Есть ли что-то о том, как работает процесс IAP, что вызывает проблемы с базовыми потоками?

Прямо сейчас спиннер вращается между моментом нажатия кнопки Buy и появляется первое предупреждение («Хотите купить? ...»), но после этого спиннер не появляется.

Вот файл .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 ]

2 голосов
/ 17 ноября 2010

Крис, 2 вещи: -

Во-первых, почему вы выбрали многопоточный подход?

Ничто здесь не требует от вас создания новой темы. API StoreKit является асинхронным, как вы знаете, вы, в конце концов, используете обратные вызовы и делегаты. Это сделано специально, чтобы не блокировать основной поток, и вам не нужно создавать новый поток. Он почти наверняка работает в фоновом потоке - но вам не нужно это знать, он обрабатывается для вас. Фактически, этот код не только не требует фонового потока, вы почти наверняка испытываете значительные затраты производительности, порождая новые потоки для выполнения такой небольшой работы. то есть. (вероятно) потребуется больше времени для запуска потока, чем для выполнения запланированной вами работы.

Итак, если вашей мотивацией было выступление, вы будете разочарованы.

Во-вторых, ваш код многопоточности или его отсутствие беспорядок. С другой стороны, просто повторюсь, ничего из этого не нужно, так что никаких больших проблем.

Вы говорите, что вы

с использованием одного и того же многопоточного кода во многих в других местах и ​​работает нормально

  • Тебе не повезло. Это создало у вас впечатление, что это должно сработать, когда оно совершенно небезопасно. Потоки действительно сложны, и если вы хотите это сделать, вы можете сделать хуже, чем читать некоторые связанные документы Apple

Threading

параллелизм

Я не хочу просто извергать материал прямо из этих руководств, перевести через мой туманный мозг и попытаться передать это как мой собственный совет, но чтобы попытаться мотивировать вас читать руководства, к которым я добавил некоторые комментарии пара строк вашего кода: -

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

Итак, я хотел бы добавить, что я определенно не эксперт в том, как работает покупка приложений, но могу поспорить, что в этом нет ничего особенного. Вероятно, с вашим счетчиком активности все будет в порядке, если вы избавитесь от фонового потока или повторно внедрите его безопасным для потока способом (что, на мой взгляд, здесь не выглядит проблемой).

1 голос
/ 18 ноября 2010

Я только просмотрел ваш код, но видел как минимум два места, где выглядело, как будто вы пытаетесь обновить пользовательский интерфейс (остановка счетчика, обновление текста метки) из метода, который вызывается из потока, который не является основным главная) нить. Это недопустимо - все пользовательские интерфейсы должны обновляться из основного потока.

Если вам нужно обновить пользовательский интерфейс из фонового потока, вам нужно перенаправить вызов в основной поток, возможно, используя peformSelectorOnMainThread: withObject:

Так, например, похоже, что loadStore вызывается из неосновного потока, и он вызывает 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];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...