Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.

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.'

Does anyone know how to fix this issue?

Stop calling UIKit from a secondary thread.

It’s hard to say exactly how you’re doing this because you’ve given us no info on what GIFMIAPHelper does. However, my general advice is that you enable the Main Thread Checker. This will stop your app as soon as it misbehaves, and you can then work out the cause from the backtrace in the debugger.

See Diagnosing Memory, Thread, and Crash Issues Early more more.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
It calls it below here.

Code Block - (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);
  }
}



Here is the rest of the file:

Code Block #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


Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
 
 
Q