2010-11-13 82 views
2

讓我先說,我的應用程序內購買作品。Threading/UIActivityIndi​​catorView with 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

克里斯,兩件事情: -

首先,爲什麼你選擇了一個多線程的方法嗎?

這裏沒有什麼需要你產生一個新的線程。 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]; 

所以,我想補充一點,我絕對不是專家如何在應用程序購買的作品,但我敢打賭沒有什麼特別的。如果你擺脫後臺線程或者以線程安全的方式重新實現它(在我看來這看起來不值得麻煩),你的活動微調器可能會很好。

1

我只剔除了你的代碼,但是我看到至少有兩個地方看起來像你試圖從一個非主線程調用的方法中更新UI(停止微調,更新標籤文本) (主)線程。這是不允許的 - 所有UI必須從主線程更新。

如果你需要從你需要馬歇爾調用主線程後臺線程更新UI,可能使用peformSelectorOnMainThread:withObject:

因此,舉例來說,它看起來像loadStore從非叫主線程,它會調用doneSpinning,它會更新UI。我會做出以下更改:

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