【问题标题】:Strange crash on in-app purchase on iOSiOS 上的应用内购买出现奇怪的崩溃
【发布时间】:2014-09-06 17:13:40
【问题描述】:

我有太多关于系统代码的崩溃报告。如何在我的应用程序中找到问题?

Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x81566238

libobjc.A.dylib     objc_msgSend + 5 respondsToSelector:
StoreKit            __34-[SKProductsRequest _handleReply:]_block_invoke + 446
libdispatch.dylib   _dispatch_call_block_and_release + 10
UIKit               UIApplicationMain + 1136
MyApp               main.m line 13

更新

在应用程序上处理应用内购买的完整代码:

#pragma mark In-App Purchase

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

- (void)requestItems:(NSString*)feature {
    NSSet *productIdentifiers = [NSSet setWithObject:[self getProductId:feature]];
    if ([feature isEqualToString:g100Items]) {
        if (product100ItemsRequest) {
            [product100ItemsRequest release];
            product100ItemsRequest = nil;
        }
        product100ItemsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
        product100ItemsRequest.delegate = self;
        [product100ItemsRequest start];
        // we will release the request object in the delegate callback
    } else
        if ([feature isEqualToString:gUnlimitedItems]) {
            if (productUnlimitedItemsRequest) {
                [productUnlimitedItemsRequest release];
                productUnlimitedItemsRequest = nil;
            }
            productUnlimitedItemsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
            productUnlimitedItemsRequest.delegate = self;
            [productUnlimitedItemsRequest 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:g100Items]]) {
            [button100Items setTitle:[NSString stringWithFormat:g100ItemsButton, product.localizedPrice] forState:UIControlStateNormal];
            // finally release the reqest we alloc/init’ed in requestItems
            [product100ItemsRequest release];
            product100ItemsRequest = nil;
        }
        if (product && [product.productIdentifier isEqualToString:[self getProductId:gUnlimitedItems]]) {
            [buttonUnlimitedItems setTitle:[NSString stringWithFormat:gUnlimitedItemsButton, product.localizedPrice] forState:UIControlStateNormal];
            // finally release the reqest we alloc/init’ed in requestItems
            [productUnlimitedItemsRequest release];
            productUnlimitedItemsRequest = 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:(BOOL)tryAgain {
    if (tryAgain) {
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        NSMutableArray *oldProducts = products;
        products = [[[NSMutableArray alloc] initWithObjects: nil] retain];
        [oldProducts release];
    }
    // 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 requestItems:g100Items];
    [self requestItems:gUnlimitedItems];
}

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

// kick off the upgrade transaction
- (void)purchaseItems:(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];
                // Calling AppStore Dialog
            }
            ok = true;
            break;
        }
    }
    if (!ok) {
        [self loadStore:YES];
        [self showAlert:gInAppAlertTitle alertStr:gNoProductsToMakePurchase];
        return;
    }
}

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

// enable pro features
- (bool)provideContent:(NSString *)productId {
    if (productId) {
        FirstViewController* firstViewController = [[tabBarController viewControllers] objectAtIndex:gSourceTabIndex];

        if ([productId isEqualToString:[self getProductId:g100Items]]) {
            firstViewController.itemCount += 100;
            [firstViewController saveItems];
            // 100 Items Provided
            return true;
        } else
            if ([productId isEqualToString:[self getProductId:gUnlimitedItems]]) {
                firstViewController.itemCount = gItemUnlimitedCount;
                [firstViewController saveItems];
                // Unlimited Items Provided
                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 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:gInAppAlertTitle 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)process100Items:(id)sender {
    if ([self canMakePurchases]) {
        [self purchaseItems:g100Items];
    } else {
        [self showAlert:gInAppAlertTitle alertStr:gCanNotMakePurchases];
    }
    // Buy 100 Items Button Pressed
}

- (IBAction)processUnlimitedItems:(id)sender {
    if ([self canMakePurchases]) {
        [self purchaseItems:gUnlimitedItems];
    } else {
        [self showAlert:gInAppAlertTitle alertStr:gCanNotMakePurchases];
    }
    // Buy Unlimited Items Button Pressed
}

- (IBAction)restoreCompletedTransactions:(id)sender {
    if ([products count] == 0) {
        [self loadStore:YES];
        [self showAlert:gInAppAlertTitle alertStr:gNoProductsToMakePurchase];
        return;
    }
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

【问题讨论】:

  • 请务必提供相关代码。没有它很难调试崩溃。
  • @max_ 我已经添加了完整的代码。感谢您的关注。
  • @Altaveron 不是 release 用于非 ARC 项目吗?
  • @carlodurso 该项目不是 ARC。

标签: ios crash in-app-purchase crash-reports


【解决方案1】:

作为块传递给handleReply 的代码尝试调用已释放对象的方法。如果没有提供的堆栈跟踪以外的更多信息,则可能无话可说。

【讨论】:

  • 不幸的是,我在项目中没有带有handleReply 子字符串的代码。
  • 检查与StoreKit 代码一起使用的任何块是否存在内存管理问题。这并不意味着你调用了handleReply,这可能是StoreKit框架中的链式调用。
  • 不幸的是,我没有将任何代码作为参数传递给StoreKit 函数。我只有一个经典的应用内购买实现。
  • 无论您对应用内购买实施做什么,都存在一个错误。如果您没有任何代码或更详细的分析,则无法添加任何内容。类似的崩溃在这里:stackoverflow.com/questions/3324596/…discussions.apple.com/message/25174184
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-10
  • 1970-01-01
  • 2012-09-28
  • 1970-01-01
相关资源
最近更新 更多