2014-06-07 95 views
2

我有以下代碼將應用內購買添加到應用中。所有的作品完美地在我的設備上。但是當我加入Crashlytics支持的應用程序時,我每天都有數百個崩潰報告。爲什麼?我真的不明白提供的代碼有什麼問題。關於應用內購買代碼的奇怪崩潰報告

#define kInAppPurchaseManagerProductsFetchedNotification @"kInAppPurchaseManagerProductsFetchedNotification" 
#define kInAppPurchaseManagerTransactionFailedNotification @"kInAppPurchaseManagerTransactionFailedNotification" 
#define kInAppPurchaseManagerTransactionSucceededNotification @"kInAppPurchaseManagerTransactionSucceededNotification" 
#define gFullVersion @"%@.FullVersion" 

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 
    if (self) { 
     [self loadStore]; 
    } 
} 

- (NSString*)getProductId:(NSString*)feature { 
    NSBundle *bundle = [NSBundle mainBundle]; 
    NSDictionary *info = [bundle infoDictionary]; 
    NSString *bundleIdentifier = [info objectForKey: @"CFBundleIdentifier"]; 
    return bundleIdentifier; 
} 

- (void)requestProducts:(NSString*)feature { 
    NSSet *productIdentifiers = [NSSet setWithObject:[self getProductId:feature]]; 
    if ([feature isEqualToString:gFullVersion]) { 
     if (productFullVersionRequest) { 
      [productFullVersionRequest release]; 
      productFullVersionRequest = nil; 
     } 
     productFullVersionRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; 
     productFullVersionRequest.delegate = self; 
     [productFullVersionRequest start]; 
     // we will release the request object in the delegate callback 
    } 
} 

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { 
    [products addObjectsFromArray:response.products]; 
    for (SKProduct *product in response.products) { 
     if (product && [product.productIdentifier isEqualToString:[self getProductId:gFullVersion]]) { 
      // finally release the reqest we alloc/init’ed in requestCompilations 
      [productFullVersionRequest release]; 
      productFullVersionRequest = nil; 
     } 
    } 

    for (NSString *invalidProductId in response.invalidProductIdentifiers) { 
     NSLog(@"Invalid product id: %@" , invalidProductId); 
    } 

    [[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil]; 
} 

// call this method once on startup 
- (void)loadStore { 
    // restarts any purchases if they were interrupted last time the app was open 
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 
    // get the product description (defined in early sections) 
    [self requestProducts:gFullVersion]; 
} 

// call this before making a purchase 
- (BOOL)canMakePurchases { 
    return [SKPaymentQueue canMakePayments]; 
} 

// kick off the upgrade transaction 
- (void)purchaseProduct:(NSString*)feature { 
    bool ok = false; 
    for (SKProduct *product in products) { 
     if ([product.productIdentifier isEqualToString:[self getProductId:feature]]) { 
      SKPayment *payment = [SKPayment paymentWithProduct:product]; 
      if (payment) { 
       [[SKPaymentQueue defaultQueue] addPayment:payment]; 
       break; 
      } 
     } 
    } 
} 

// saves a record of the transaction by storing the receipt to disk 
- (void)recordTransaction:(SKPaymentTransaction *)transaction { 
    if ([transaction.payment.productIdentifier isEqualToString:[self getProductId:gFullVersion]]) { 
     // save the transaction receipt to disk 
     [[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:[self getProductId:gFullVersion]]; 
     [[NSUserDefaults standardUserDefaults] synchronize]; 
    } 
} 

// enable pro features 
- (bool)provideContent:(NSString *)productId { 
    if ([productId isEqualToString:[self getProductId:gFullVersion]]) { 
     // ...provide content here... 
     return true; 
    } 
    return false; 
} 

// removes the transaction from the queue and posts a notification with the transaction result 
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful { 
    // 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]; 
    } 
} 

// called when the transaction was successful 
- (void)completeTransaction:(SKPaymentTransaction *)transaction { 
    [self recordTransaction:transaction]; 
    bool provided = [self provideContent:transaction.payment.productIdentifier]; 
    [self finishTransaction:transaction wasSuccessful:YES]; 
} 

// called when a transaction has been restored and and successfully completed 
- (void)restoreTransaction:(SKPaymentTransaction *)transaction { 
    [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 { 
    if (transaction.error.code != SKErrorPaymentCancelled) { 
     // error! 
     [self finishTransaction:transaction wasSuccessful:NO]; 
     [self showAlert:NSLocalizedString(@"InAppPurchase", @"") alertStr:[transaction.error localizedDescription]]; 
    } else { 
     // this is fine, the user just cancelled, so don’t notify 
     [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 
    } 
} 

// called when the transaction status is updated 
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { 
    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; 
     } 
    } 
} 

- (IBAction)processFullVersion:(id)sender { 
    if ([self canMakePurchases]) { 
     [self purchaseProduct:gFullVersion]; 
    } else { 
     [self showAlert:NSLocalizedString(@"InAppPurchase", @"") alertStr:NSLocalizedString(@"CanNotMakePurchases", @"")]; 
    } 
} 

- (IBAction)restoreCompletedTransactions:(id)sender { 
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; 
} 

的崩潰報告:

Crashed: com.apple.main-thread 
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0xa1c57ae2 
Thread : Crashed: com.apple.main-thread 
0 libobjc.A.dylib    0x3bf5e5d0 objc_msgSend + 15 
1 StoreKit      0x360260a7 __NotifyObserverAboutChanges + 66 
2 CoreFoundation     0x341aeacd CFArrayApplyFunction + 176 
3 StoreKit      0x36026055 -[SKPaymentQueue _notifyObserversAboutChanges:sendUpdatedDownloads:] + 128 
4 StoreKit      0x36024bc9 -[SKPaymentQueue addPayment:] + 464 
5 MyApplication     0x000a76d7 -[CalendarView purchaseProduct:] (CalendarView.m:952) 
6 MyApplication     0x000a8c17 -[CalendarView processFullVersion:] (CalendarView.m:1107) 
7 MyApplication     0x000a361b -[CalendarView clickHandler:] (CalendarView.m:458) 
8 MyApplication     0x000a55cf -[CalendarView liteVersionDisplayAlert] (CalendarView.m:671) 
9 MyApplication     0x000a5d2f -[CalendarView toggleView:] (CalendarView.m:735) 
10 MyApplication     0x000a625f -[CalendarView modeButtonPressed] (CalendarView.m:795) 
11 MyApplication     0x000c245f -[ToolbarView modePressed] (ToolbarView.m:304) 
12 UIKit       0x36162087 -[UIApplication sendAction:to:from:forEvent:] + 70 
13 UIKit       0x3616203b -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 30 
14 UIKit       0x36162015 -[UIControl sendAction:to:forEvent:] + 44 
15 UIKit       0x361618cb -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 502 
16 UIKit       0x36161db9 -[UIControl touchesEnded:withEvent:] + 488 
17 UIKit       0x3608a5f9 -[UIWindow _sendTouchesForEvent:] + 524 
18 UIKit       0x360778e1 -[UIApplication sendEvent:] + 380 
19 UIKit       0x360771ef _UIApplicationHandleEvent + 6198 
20 GraphicsServices    0x37d8f5f7 _PurpleEventCallback + 590 
21 GraphicsServices    0x37d8f227 PurpleEventCallback + 34 
22 CoreFoundation     0x3423d3e7 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 34 
23 CoreFoundation     0x3423d38b __CFRunLoopDoSource1 + 138 
24 CoreFoundation     0x3423c20f __CFRunLoopRun + 1382 
25 CoreFoundation     0x341af23d CFRunLoopRunSpecific + 356 
26 CoreFoundation     0x341af0c9 CFRunLoopRunInMode + 104 
27 GraphicsServices    0x37d8e33b GSEventRunModal + 74 
28 UIKit       0x360cb2b9 UIApplicationMain + 1120 
29 MyApplication     0x0007daff main (main.m:13) 

非常感謝您的幫助!

回答

11

您似乎已將您添加到付款隊列的觀察員取消分配。您撥打SKPaymentQueue addTransactionObserver:,但您永遠不會撥打SKPaymentQueue removeTransactionObserver:

添加dealloc方法到您的視圖控制器:

- (void)dealloc { 
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; 
} 

最有可能您的應用程序顯示其自身添加作爲觀察員視圖控制器。然後,該視圖控制器被解僱(但仍標記爲觀察員)。之後您會顯示控制器的另一個實例並執行一些付款交易。支付隊列然後嘗試通知新的和新的觀察員。發生崩潰是因爲舊觀察者早已被釋放。

通過添加上面的dealloc方法,此問題將得到解決。

+0

但是我的應用程序在運行時只創建一次view(觀察者)。下次啓動應用程序時會發生崩潰嗎? – Dmitry

-2

它看起來像你正在使用鍵值觀測。當應用程序運行這一部分

- (void)purchaseProduct:(NSString*)feature { 
    bool ok = false; 
    for (SKProduct *product in products) { 
     if ([product.productIdentifier isEqualToString:[self getProductId:feature]]) { 
      SKPayment *payment = [SKPayment paymentWithProduct:product]; 
      if (payment) { 
       [[SKPaymentQueue defaultQueue] addPayment:payment]; 
       break; 
      } 
     } 
    } 
} 

,併成功將其添加進更新的觀察者認爲是不存在的,因此壞的內存訪問支付我猜。

我會檢查[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

+0

我無法檢查並修復Apple的代碼。 – Dmitry

+0

代碼中哪裏可以看到正在使用KVO? – rmaddy

+0

SKPaymentQueue是否使用KVO進行更新?無論如何,我沒有意識到它是蘋果圖書館的一部分..我認爲他已經做了他自己的addTransactionObserver的實現: – Literphor