2014-06-07 2 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

하지만 내 응용 프로그램은 실행시 한 번만 뷰 (관찰자)를 만듭니다. 응용 프로그램이 다음에 실행될 때 충돌이 발생합니까? – 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