2012-08-26 4 views
1

클라이언트가 기존 앱을 가져 왔으며 크래시 보고서에 대한 Crittercism을 포함한 새 버전을 방금 출시했습니다.SIGSEGV StoreKit에서 충돌 - 복제 할 수 없습니다.

출시 이후, 우리는 아래와 같이 충돌 보고서를로드하고 있습니다. 그 충돌은 SKProductRequest의 대표가 너무 빨리 발표했기 때문에 발생했습니다. 그래서 충돌이 일어나는 이유에 대한 답변을 찾고 있지 않습니다. 이미 StackOverflow에서 다른 곳으로 답변을 받았습니다.

내 문제는 내가 버그를 복제 할 수 없다는 것입니다. 수많은 기기와 iOS의 다른 버전을 사용해 보았습니다. Crittercism에서 충돌은 주로 최신 장치 및 iPhone, iPod 및 iPad의 범위에서 발생합니다. 따라서이 장치는 특정 유형의 장치가 아니지만 계속 발생시킬 수는 없습니다. 나는 Lite 버전을 다운로드했고 거기에서 Full 버전을 구입했습니다. 완벽하게 작동합니다.

내 질문에 그러므로 누군가 내 장치에서 일어날 수 있도록 어떻게 해결할 수 있는지 알 수 있습니까?!

libobjc.A.dylib 0x37393f78 objc_msgSend + 15 
StoreKit 0x37bc3a4f -[SKProductsRequest handleFinishResponse:returningError:] + 142 
StoreKit 0x37bc4dc7 -[SKRequest _requestFinishedNotification:] + 210 
Foundation 0x319624ff __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 18 
CoreFoundation 0x31027547 ___CFXNotificationPost_block_invoke_0 + 70 
CoreFoundation 0x30fb3097 _CFXNotificationPost + 1406 
Foundation 0x318d63eb -[NSNotificationCenter postNotificationName:object:userInfo:] + 66 
AppSupport 0x314eeba3 -[CPDistributedNotificationCenter deliverNotification:userInfo:] + 62 
AppSupport 0x314f010b _CPDNDeliverNotification + 290 
AppSupport 0x314ee99b _XDeliverNotification + 170 
AppSupport 0x314e3b11 migHelperRecievePortCallout + 172 
CoreFoundation 0x3102f523 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 38 
CoreFoundation 0x3102f4c5 __CFRunLoopDoSource1 + 140 
CoreFoundation 0x3102e313 __CFRunLoopRun + 1370 
CoreFoundation 0x30fb14a5 CFRunLoopRunSpecific + 300 
CoreFoundation 0x30fb136d CFRunLoopRunInMode + 104 
GraphicsServices 0x3302b439 GSEventRunModal + 136 
UIKit 0x30714cd5 UIApplicationMain + 1080 
MyAppLite 0x000fc7c3 main (main.m:13) 

범인은 여기 어딘가에있을 수밖에 없다,하지만, 난 여전히 내 장치에 충돌 할 수없는, 또는 시뮬레이터 :

#import "InAppPurchaseViewController.h" 

#define INDICATOR_Y 150.0f 
#define INDICATOR_MOVE_Y 300.0f 
#define PRODUCT_LABEL_Y 150.0f 
#define PURCHASE_BUTTON_Y 190.0f 
#define RESTORE_BUTTON_Y 240.0f 

@interface InAppPurchaseViewController (Private) 
- (void)updateUIToDefaultState; 
@end 

@implementation InAppPurchaseViewController 

- (void) dealloc { 
    [productLabel release]; 
    [purchaseButton release]; 
    [indicatorView release]; 
    [super dealloc]; 
} 

#pragma mark - 
#pragma mark UIViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    self.title = @"..."; 
    self.view.backgroundColor = [UIColor blackColor]; 
    self.tableView.backgroundColor = [UIColor blackColor]; 
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 
    self.tableView.scrollEnabled = NO; 
    self.tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; 

    UIImageView *headerImageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"upgrade-header.png"]] autorelease]; 
    UIView *headerView = [[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, headerImageView.image.size.height + 200.0)] autorelease]; 
    [headerView addSubview:headerImageView]; 

    indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 
    indicatorView.hidesWhenStopped = YES; 
    [indicatorView startAnimating]; 
    [headerView addSubview:indicatorView]; 

    productLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 0.0f)]; 
    productLabel.text = @"..."; 
    productLabel.hidden = YES; 
    productLabel.font = [UIFont systemFontOfSize:18.0f]; 
    productLabel.textColor = [UIColor whiteColor]; 
    productLabel.backgroundColor = [UIColor blackColor]; 
    [productLabel sizeToFit]; 
    [headerView addSubview:productLabel]; 

    purchaseButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; 
    purchaseButton.titleLabel.font = [UIFont boldSystemFontOfSize:20.0f]; 
    UIImage *bgImage = [UIImage imageNamed:@"btn_purchase.png"]; 
    UIImage *buttonImage = [bgImage stretchableImageWithLeftCapWidth:(bgImage.size.width/2.0f) - 1 topCapHeight:0.0f]; 
    [purchaseButton setBackgroundImage:buttonImage forState:UIControlStateNormal]; 
    [purchaseButton setTitle:@"Purchase" forState:UIControlStateNormal]; 
    [purchaseButton addTarget:self action:@selector(purchaseClicked:) forControlEvents:UIControlEventTouchUpInside]; 
    purchaseButton.frame = CGRectMake(0.0f, 0.0f, 300.0f, buttonImage.size.height); 
    purchaseButton.hidden = YES; 
    [self.view addSubview:purchaseButton]; 

    restoreButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; 
    restoreButton.enabled = NO; 
    restoreButton.titleLabel.font = [UIFont boldSystemFontOfSize:18.0f]; 
    [restoreButton setBackgroundImage:buttonImage forState:UIControlStateNormal]; 
    [restoreButton setTitle:@"Restore Purchases" forState:UIControlStateNormal]; 
    [restoreButton addTarget:self action:@selector(restoreClicked:) forControlEvents:UIControlEventTouchUpInside]; 
    restoreButton.frame = CGRectMake(0.0f, 0.0f, 300.0f, buttonImage.size.height); 
    [self.view addSubview:restoreButton]; 

    headerImageView.center = headerView.center; 
    headerImageView.frame = CGRectMake(headerImageView.frame.origin.x, 0.0f, headerImageView.frame.size.width, headerImageView.frame.size.height); 

    indicatorView.center = headerView.center; 
    indicatorView.frame = CGRectMake(indicatorView.frame.origin.x, INDICATOR_Y, indicatorView.frame.size.width, indicatorView.frame.size.height); 

    productLabel.center = headerView.center; 
    productLabel.frame = CGRectMake(productLabel.frame.origin.x, PRODUCT_LABEL_Y, productLabel.frame.size.width, productLabel.frame.size.height); 

    purchaseButton.center = headerView.center; 
    purchaseButton.frame = CGRectMake(purchaseButton.frame.origin.x, PURCHASE_BUTTON_Y, purchaseButton.frame.size.width, purchaseButton.frame.size.height); 

    restoreButton.center = headerView.center; 
    restoreButton.frame = CGRectMake(restoreButton.frame.origin.x, RESTORE_BUTTON_Y, restoreButton.frame.size.width, restoreButton.frame.size.height); 

    self.tableView.tableHeaderView = headerView; 

    [self performSelectorInBackground:@selector(retrieveProductDetails:) withObject:nil]; 
} 

#pragma mark - 
#pragma mark AppStoreServiceDelegate 

- (void)productDetailsRequestSucceededWithResponse:(SKProductsResponse *)response { 
    if (response.products.count == 1) { 
     [self performSelectorOnMainThread:@selector(productDetailsRetrieved:) withObject:[response.products objectAtIndex:0] waitUntilDone:NO];  
    } else { 
     NSString *message = @"Unable to retrieve product details: No valid product to purchase. Please contact support."; 
     [self performSelectorOnMainThread:@selector(appStoreRequestFailed:) withObject:message waitUntilDone:NO]; 
    } 
} 

- (void)productDetailsRequestFailedWithError:(NSError *)error { 
    NSString *message = [NSString stringWithFormat:@"Unable to retrieve product details: %@", [error localizedDescription]]; 
    [self performSelectorOnMainThread:@selector(appStoreRequestFailed:) withObject:message waitUntilDone:NO]; 
} 

- (void)transactionSucceededForProductId:(NSString *)productId { 
    [self performSelectorOnMainThread:@selector(purchaseCompleted:) withObject:productId waitUntilDone:NO]; 
} 

- (void)transactionFailedWithReason:(NSString *)reason { 
    NSString *message = [NSString stringWithFormat:@"Sorry, your purchase could not be completed: %@", reason]; 
    [self performSelectorOnMainThread:@selector(appStoreRequestFailed:) withObject:message waitUntilDone:NO]; 
} 

- (void)transactionCancelled { 
    [self performSelectorOnMainThread:@selector(updateUIToDefaultState) withObject:nil waitUntilDone:NO]; 
} 

#pragma mark - 
#pragma mark UIAlertViewDelegate (purchase failed) 
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 
    [self.navigationController popViewControllerAnimated:YES]; 
} 

#pragma mark - 
#pragma mark Private button callbacks 

- (void)purchaseClicked:(UIButton *)clicked { 
    clicked.enabled = NO; 
    [indicatorView startAnimating]; 
    indicatorView.frame = CGRectMake(indicatorView.frame.origin.x, INDICATOR_MOVE_Y, indicatorView.frame.size.width, indicatorView.frame.size.height); 
    [self performSelectorInBackground:@selector(purchaseProduct:) withObject:nil]; 
} 

- (void)restoreClicked:(UIButton *)clicked { 
    clicked.enabled = NO; 
    [indicatorView startAnimating]; 
    indicatorView.frame = CGRectMake(indicatorView.frame.origin.x, INDICATOR_MOVE_Y, indicatorView.frame.size.width, indicatorView.frame.size.height); 
    [self performSelectorInBackground:@selector(restoreProducts:) withObject:nil]; 
} 

#pragma mark - 
#pragma mark Private 

- (void)showPurchaseDetailsWithName:(NSString *)productName price:(NSString *)price { 
    self.title = productName; 
    productLabel.text = [NSString stringWithFormat:@"%@, %@", productName, price]; 
    [productLabel sizeToFit]; 
    productLabel.center = self.tableView.tableHeaderView.center; 
    [self updateUIToDefaultState]; 
} 

- (void)productDetailsRetrieved:(SKProduct *)productDetails { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [indicatorView stopAnimating]; 
    [self showPurchaseDetailsWithName:productDetails.localizedTitle price:[[AppStoreService sharedAppStoreService] formatPrice:productDetails]]; 
    [pool drain]; 
} 

- (void)purchaseCompleted:(NSString *)productId { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [indicatorView stopAnimating]; 
    [[AppStoreService sharedAppStoreService] setPurchasedFullEdition:YES]; 
    [self.navigationController popViewControllerAnimated:YES]; 
    [pool drain]; 
} 

- (void)purchaseProduct:(id)ignored { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [[AppStoreService sharedAppStoreService] purchaseProducts:[NSSet setWithObject:[[AppStoreService sharedAppStoreService] inAppProductIdentifierForEdition]] notifyingDelegate:self]; 
    [pool drain]; 
} 

- (void)restoreProducts:(id)ignored { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [[AppStoreService sharedAppStoreService] retoreCompletedTransactionsNotifyingDelegate:self]; 
    [pool drain]; 
} 

- (void)appStoreRequestFailed:(NSString *)reason { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [[[[UIAlertView alloc] initWithTitle:@"Purchase Error" message:reason delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; 
    [self updateUIToDefaultState]; 
    [pool drain]; 
} 

- (void)retrieveProductDetails:(id)ignored { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [[AppStoreService sharedAppStoreService] requestDetailsOfProducts:[NSSet setWithObject:[[AppStoreService sharedAppStoreService] inAppProductIdentifierForEdition]] notifyingDelegate:self]; 
    [pool drain]; 
} 

- (void)updateUIToDefaultState { 
    [indicatorView stopAnimating]; 
    productLabel.hidden = NO; 
    purchaseButton.hidden = NO; 
    restoreButton.enabled = YES; 
    purchaseButton.enabled = YES; 
} 

@end 

여기 AppStoreService.m

있어
// AppStoreService.m 
    #import "AppStoreService.h" 
    #import "SynthesizeSingleton.h" 
    #import "DataService.h" 
    #import "ConfigService.h" 

    #pragma mark - 
    #pragma mark Private internal app store delegates 

    @implementation AppStoreProductRequestDelegate 

    @synthesize delegate = _delegate; 

    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { 
     //NSLog(@"Got response... %@", response); 
     [request release]; 
     if (self.delegate) { 
      [self.delegate productDetailsRequestSucceededWithResponse:response]; 
     } 
    } 

    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { 
     //NSLog(@"Got error... %@", error); 
     [request release]; 
     if (self.delegate) { 
      [self.delegate productDetailsRequestFailedWithError:error]; 
     } 
    } 

    @end 

    @implementation AppStoreTransactionObserver 

    @synthesize delegate = _delegate; 

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

    - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { 
     if (self.delegate) { 
      [self.delegate transactionFailedWithReason:[NSString stringWithFormat:@"Purchase failed: %@", [error localizedDescription]]]; 
     } 
    } 

    - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { 
     //NSLog(@"paymentQueueRestoreCompletedTransactionsFinished:"); 
    } 

    - (void)failedTransaction:(SKPaymentTransaction *)transaction { 
     //NSLog(@"failedTransaction: %@", transaction); 
     if (self.delegate) { 
      if (transaction.error.code == SKErrorPaymentCancelled) { 
       [self.delegate transactionCancelled]; 
      } else { 
       [self.delegate transactionFailedWithReason:[NSString stringWithFormat:@"Purchase failed: %@", [transaction.error localizedDescription]]]; 
      } 
     } 
     [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 
    } 

    - (void)restoreTransaction:(SKPaymentTransaction *)transaction { 
     //NSLog(@"restoreTransaction: %@", transaction); 
     if (self.delegate) { 
      [self.delegate transactionSucceededForProductId:transaction.originalTransaction.payment.productIdentifier]; 
     } 
     [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 
    } 

    - (void)completeTransaction:(SKPaymentTransaction *)transaction { 
     //NSLog(@"completeTransaction: %@", transaction); 
     if (self.delegate) { 
      [self.delegate transactionSucceededForProductId:transaction.payment.productIdentifier]; 
     } 
     [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 
    } 

    @end 

    #pragma mark - 
    #pragma mark AppStoreService 

    @implementation AppStoreService 

    SYNTHESIZE_SINGLETON_FOR_CLASS(AppStoreService); 

    static NSString *kLPHasPurchasedFullEdition = @"kLPHasPurchasedFullEdition"; 

    - (AppStoreService *)init { 
     if (self = [super init]) { 
      productDetailsDelegate = [[AppStoreProductRequestDelegate alloc] init]; 
      appStoreObserver = [[AppStoreTransactionObserver alloc] init]; 
      [[SKPaymentQueue defaultQueue] addTransactionObserver:appStoreObserver]; 
     } 
     return self; 
    } 

    - (void) dealloc { 
     [[SKPaymentQueue defaultQueue] removeTransactionObserver:appStoreObserver]; 
     [productDetailsDelegate release]; 
     [appStoreObserver release]; 
     [super dealloc]; 
    } 

    - (BOOL)hasPurchasedFullEdition { 
     return [[NSUserDefaults standardUserDefaults] boolForKey:kLPHasPurchasedFullEdition]; 
    } 

    - (void)setPurchasedFullEdition:(BOOL)purchased { 
     //NSLog(@"Purchased? %d", purchased); 
     [[NSUserDefaults standardUserDefaults] setBool:purchased forKey:kLPHasPurchasedFullEdition]; 
     [[ConfigService sharedConfigService] synchronizeConfig]; 
    } 

    - (NSString *)inAppProductIdentifierForEdition { 
     if ([[DataService sharedDataService] isLiteEdition]) { 
      if ([DataService sharedDataService].isLanguageEdition) { 
       // Note. Remove the "-" from language codes, e.g. Brazillian Portugese pt-br, as in-app purchase IDs cannot contain a hyphen. 
       NSString *fixedCode = [[DataService sharedDataService].languageCode stringByReplacingOccurrencesOfString:@"-" withString:@""]; 
       return [NSString stringWithFormat:@"com.myBrokenApp.%@.AllCategories", fixedCode]; 
      } else { 
       return @"com.myBrokenApp.AllCategories"; 
      } 
     } else { 
      @throw [NSException exceptionWithName:@"InvalidOperation" reason:@"An in app purchase product ID was requested for a non-lite version" userInfo:[NSDictionary dictionary]]; 
     } 
    } 

    - (NSString *)purchasedCategoryIdsWhereClause { 
     // flirting & essentials for lite builds 
     return ([DataService sharedDataService].isLiteEdition && ![self hasPurchasedFullEdition]) ? @"and c.category_id in (1,19)" : @" "; 
    } 

    - (NSString *)formatPrice:(SKProduct *)product { 
     NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 
     [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; 
     [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 
     [numberFormatter setLocale:product.priceLocale]; 
     NSString *currency = [numberFormatter stringFromNumber:product.price]; 
     [numberFormatter release]; 
     return currency; 
    } 

    - (void)requestDetailsOfProducts:(NSSet *)products notifyingDelegate:(id<AppStoreServiceDelegate>)delegate { 
     //NSLog(@"Retrieving details of products: %@", products); 
     SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products]; 
     productDetailsDelegate.delegate = delegate; 
     request.delegate = productDetailsDelegate; 
     //NSLog(@"Starting request..."); 
     [request start]; 
    } 

    - (void)purchaseProducts:(NSSet *)products notifyingDelegate:(id<AppStoreServiceDelegate>)delegate { 
     //NSLog(@"Making in app purchase for products: %@", products); 
     if ([SKPaymentQueue canMakePayments]) { 
      appStoreObserver.delegate = delegate; 
      for (NSString *productId in products) { 
       [[SKPaymentQueue defaultQueue] addPayment:[SKPayment paymentWithProductIdentifier:productId]]; 
      } 

     } else { 
      [delegate transactionFailedWithReason:@"You are not permitted to make purchases."]; 
     } 
    } 

    - (void)retoreCompletedTransactionsNotifyingDelegate:(id<AppStoreServiceDelegate>)delegate { 
     //NSLog(@"Restoring in-app purchases..."); 
     if ([SKPaymentQueue canMakePayments]) { 
      appStoreObserver.delegate = delegate; 
      [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; 
     } else { 
      [delegate transactionFailedWithReason:@"You are not permitted to make purchases."]; 
     } 
    } 

    @end 
+0

간헐적 인 충돌은 일반적으로 스레드 문제를 나타냅니다. 나는 당신의 스레딩 논리를 두 번 점검하고 그 논리가 확실한 지, 문제가 해결 될 가능성이 있는지 확인합니다. – Stunner

답변

1

휙휙, 여기에 매우 의심스러운 코드를 게시했습니다. 한 가지로 SYNTHESIZE_SINGLETON_FOR_CLASS()를 사용하면 AppStoreService dealloc 메서드가 호출되지 않으므로 앱에서 메모리 누수가 발생한다는 의미입니다. 또한 productsRequest에서 [릴리스 요청]에 대한 호출은 실제로 매우 이상하게 보입니다. 그러나 SKProductsRequest와 관련된 특정 문제는 SKProductsRequest가 콜백을 호출하기 전에 할당 취소 된 개체 중 하나를 가리키는 대리자와 관련이있는 것으로 보입니다. 이는 위임자 역할을하는 객체가 dealloc 메소드를 호출 할 때 델리게이트를 nil로 설정해야 함을 나타냅니다. 문제를 재현하는 방법은 코드에서 설정 한 정말 이상한 개체 수명 때문에 매우 까다로울 수 있습니다. 자동 회귀 테스트 도구를 사용하는 것이 좋겠지 만, 싱글 톤을 사용하기 때문에 앱의 개체 수명을 확인하는 데에도 도움이 될지 잘 모르겠습니다. What is so bad about singletons?

+0

나는 동의하고, 주에 감사드립니다. 이전 개발자 코드를 모두 다시 작성하지 않으려 고 노력했지만 잘 될 수도 있습니다. 이 오래된 질문에 대한 답변으로 투표 해 드리겠습니다. 모든 것을 다시 작성하는 사례가있는 것 같지만 더 좋습니다! – siburb

1

나는 "귀하의 이론"이 너무 빨리 발표되는 SKProductRequest의 대리인 "이라고 생각합니다. 구매 도중에 응용 프로그램을 종료/종료하려고 했습니까? 샌드 박스 서버가 너무 빠르면 (트랜잭션이 완료되기 직전에 중단 점을 삽입하고 수동으로 앱을 종료하십시오.) 앱이 다시 실행되고 결국 StoreKit에 트랜잭션으로 등록됩니다 위임자는 이전 실행에서 열린 트랜잭션을 가져 와서 몇 가지 디자인 실수를 발견하고 StoreKit 트랜잭션 완료와 관련된 버그를 해결할 수있었습니다.