I make sure that the call is inside the main thread. However, I still get a crash when the information is updated from the background thread.
Does anyone know how to fix this issue?
Code Block
- (void)viewDidLoad {
[super viewDidLoad];
[self styleConfiguration];
[self registerInAppPurchaseObservers];
__weak typeof (self) weakSelf = self;
[weakSelf.activityIndicator startAnimating];
dispatch_async(dispatch_get_main_queue(), ^{
[[GIFMIAPHelper defaultHelper] requestProductsWithCompletionHandler:^(BOOL success, SKProduct *productPro, NSError *error) {
__weak typeof (self) strongSelf = weakSelf;
[weakSelf.activityIndicator stopAnimating];
if (success) {
[strongSelf updateProductProProductInfo:productPro];
} else {
[strongSelf requestProductProFailed:error.localizedDescription];
}
}];
[FIRAnalytics logEventWithName:kFIREventScreenView parameters:@{
@"Screen name": @"Product Pro Purchase",
@"Screen class": NSStringFromClass(self.class),
}];
});
}
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
Here is what it calls
- (void)requestProductsWithCompletionHandler:(GIFMRequestProductsCompletionHandler)completionHandler {
@try {
self.completionHandler = [completionHandler copy];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productsIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
@catch (NSException *exception) {
DLog(@"Failed transaction %@", exception.description);
}
}
And the rest of the file:
#import "GIFMIAPHelper.h"
#import <SecureNSUserDefaults/NSUserDefaults+SecureAdditions.h>
#define SecureSalt @"SecuredPaymentSalt"
@interface GIFMIAPHelper()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end
@implementation GIFMIAPHelper
+ (instancetype)defaultHelper {
static GIFMIAPHelper *defaultHelper;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSSet *productIdentifiers = [NSSet setWithObject:GIFMProduct_VomboPro];
defaultHelper = [[self alloc] initWithProductIdentifiers:productIdentifiers];
});
return defaultHelper;
}
- (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers {
self = [super init];
if (self) {
NSString *udid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[[NSUserDefaults standardUserDefaults] setSecret:[NSString stringWithFormat:@"%@%@", udid, SecureSalt]];
self.productsIdentifiers = productIdentifiers;
self.purchasedProductIdentifiers = [NSMutableSet set];
for (NSString *productId in productIdentifiers) {
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] secretBoolForKey:productId];
if (productPurchased) {
[self.purchasedProductIdentifiers addObject:productId];
} else {
DLog(@"Not purchased: %@", productId);
}
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)requestProductsWithCompletionHandler:(GIFMRequestProductsCompletionHandler)completionHandler {
@try {
self.completionHandler = [completionHandler copy];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productsIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
@catch (NSException *exception) {
DLog(@"Failed transaction %@", exception.description);
}
}
- (void)buyVomoboPro:(SKProduct *)product {
@try {
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
@catch (NSException *exception) {
DLog(@"Failed transaction %@", exception.description);
}
}
- (BOOL)vomboProPurchased {
#ifdef DEBUG_PREMIUM_LEVEL
return YES;
#endif
#ifdef DEBUG_FREEMIUM_LEVEL
return NO;
#endif
return [self.purchasedProductIdentifiers containsObject:GIFMProduct_VomboPro];
}
- (void)restoreVomboProPurchase {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)storeProductPurchasedState:(NSString *)productIdentifier {
[self.purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setSecretBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)completeVomboProTransaction:(SKPaymentTransaction *)transaction {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSString *productIdentifier = transaction.payment.productIdentifier;
[self storeProductPurchasedState:productIdentifier];
[[NSNotificationCenter defaultCenter] postNotificationName:GIFMIAPHelperProductPurchasedCompletedNotification object:productIdentifier];
}
- (void)vomboProTransactionFailed:(SKPaymentTransaction *)transaction {
@try {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:GIFMIAPHelperProductPurchasedFailedNotification object:transaction.error.localizedDescription];
}
@catch (NSException *exception) {
DLog(@"Failed transaction %@", exception.description);
}
}
#pragma mark - SKProductsRequestDelegate, SKPaymentTransactionObserver
- (void)productsRequest:(nonnull SKProductsRequest *)request didReceiveResponse:(nonnull SKProductsResponse *)response {
NSLog(@"Fetched products");
self.productsRequest = NULL;
NSArray *products = response.products;
for (SKProduct *product in products) {
if ([product.productIdentifier isEqualToString:GIFMProduct_VomboPro]) {
NSLog(@"Found product: %@ – Product: %@ – Price: %0.2f", product.productIdentifier, product.localizedTitle, product.price.floatValue);
if (self.completionHandler){
self.completionHandler(YES, product, nil);
}
return;
}
}
NSError *error = [NSError errorWithDomain:@"SKProductsRequestNotFound" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Vombo Pro Not Found"}];
if (self.completionHandler){
self.completionHandler(NO, NULL, error);
}
return;
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
self.productsRequest = NULL;
if (self.completionHandler){
self.completionHandler(NO, NULL, error);
}
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
DLog(@"Restore Payment Finished");
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
DLog(@"Restore PaymentQueue Failed");
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
DLog(@"Payment Updated");
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeVomboProTransaction:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(@"Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self completeVomboProTransaction:transaction];
NSLog(@"Transaction state -> Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
@end