2

まず、私のアプリ内購入は機能します。

私は1週間以上アクティビティインジケーター/スレッディングに苦労しています。スピナー(UIActivityIndi​​catorView)をInAppPurchase.m内でうまく再生するのに実際の問題があります。

私は他の多くの場所で同じスレッドコードを使用していますが、それは正常に機能します。

基本的なスレッド化で問題を引き起こすIAPプロセスの動作について何かありますか?

現在、購入ボタンをタップしてから最初のアラート(「購入しますか?...」)が表示されるまでの間、スピナーは回転しますが、その後はスピナーは回転しません。

.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
4

2 に答える 2

2

クリス、2つのこと:-

まず、なぜマルチスレッドアプローチを選択したのですか?

ここでは、新しいスレッドを生成する必要はありません。StoreKit apiは非同期です。ご存知のように、結局、コールバックとデリゲートを使用しています。これは特に、メインスレッドをブロックしないため、新しいスレッドを生成する必要がないようにするためです。ほぼ確実にバックグラウンドスレッドで動作しますが、それを知る必要はありません。自動的に処理されます。実際、このコードはバックグラウンドスレッドを必要としないだけでなく、ほとんど確実に、ほとんど作業を行わないために新しいスレッドを生成するためのかなりのパフォーマンスコストが発生しています。すなわち。スレッドの起動には、スケジュールした作業を実行するよりも(おそらく)時間がかかります。

ですから、もしあなたのモチベーションがパフォーマンスだったら、あなたはがっかりするでしょう。

第二に、あなたのスレッデッドコード、またはそれの欠如は混乱です。プラス面としては、繰り返しになりますが、どれも必要ないので、大きな問題はありません。

あなたはあなたが

他の多くの場所で同じスレッドコードを使用すると、正常に機能します

  • あなたは不運でした。これは、実際には完全に安全ではない場合にこれが機能するはずであるという印象を与えました。スレッディングは本当に難しいです、そしてあなたがそれをしたいのならあなたはいくつかの関連するアップルのドキュメントを読むよりも悪いことをするかもしれません

糸脱毛

並行性

私はこれらのガイドから直接ものを吐き出し、私の霧の脳を通して翻訳し、それを私自身のアドバイスとして渡すことを試みるのは気が進まないが、ガイドを読むようにあなたをやる気にさせるために、私はいくつかのコメントを追加しましたコードの行:-

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

ですから、私はアプリ内購入がどのように機能するかについての専門家ではないことを付け加えたいと思いますが、それについて特別なことは何もないに違いありません。バックグラウンドスレッドを削除するか、スレッドセーフな方法で再実装すれば、アクティビティスピナーはおそらく問題ありません(私の意見では、ここで問題を起こす価値はないようです)。

于 2010-11-17T13:59:49.660 に答える
1

コードをざっと見ただけですが、プライマリ(メイン)スレッドではないスレッドから呼び出されたメソッドからUIを更新(スピナーを停止し、ラベルテキストを更新)しようとしているように見える場所が少なくとも2つありました。 。これは許可されていません。すべての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];
}
于 2010-11-18T15:34:47.003 に答える