2010-11-13 6 views
2

Позвольте мне сначала сказать, что работает приложение In App Purchase.Threading/UIActivityIndicatorView с покупкой приложения

Я боюсь с индикатором активности/Threading уже более недели. У меня возникают реальные проблемы с тем, чтобы мой счетчик (UIActivityIndicatorView) играл хорошо в моем 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

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

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

Для этого ничего не требуется. StoreKit api является асинхронным, как вы знаете, после этого вы используете обратные вызовы и делегаты. Это специально так, что он не будет блокировать основной поток, и поэтому вам не нужно создавать новый поток. Это почти наверняка работает над фоновым потоком, но вам не нужно это знать, он обрабатывается для вас. Infact, не только этот код не требует фонового потока, вы почти наверняка испытываете существенную производительность, создавая новые потоки, чтобы сделать так мало работы. то есть. он (вероятно) займет больше времени для запуска потока, чем он будет выполнять работу, которую вы запланировали на нем.

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

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

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

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

  • Вы не повезло. Это создало впечатление, что это должно срабатывать, когда он становится абсолютно небезопасным. Threading действительно сложен, и если вы хотите сделать это вы можете сделать хуже, чем чтение некоторых из них переферия Apple Документов

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

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

1

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

Если вам необходимо обновить пользовательский интерфейс от фонового потока вы должны Маршаллу вызова основного потока, возможно, с помощью 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]; 
}