Threading/UIActivityIndicatorView with In App Purchase
让我先说,我的应用程序内购买作品。Threading/UIActivityIndicatorView with In App Purchase
我一直在努力活动指标/线程超过一个星期了。我有真正的问题让我的微调(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
克里斯,两件事情: -
首先,为什么你选择了一个多线程的方法吗?
这里没有什么需要你产生一个新的线程。 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];
所以,我想补充一点,我绝对不是专家如何在应用程序购买的作品,但我敢打赌没有什么特别的。如果你摆脱后台线程或者以线程安全的方式重新实现它(在我看来这看起来不值得麻烦),你的活动微调器可能会很好。
我只剔除了你的代码,但是我看到至少有两个地方看起来像你试图从一个非主线程调用的方法中更新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];
}