0

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
  • If you are able to reproduce, check https://stackoverflow.com/questions/44943995/what-is-main-thread-checker-in-xcode to force a breakpoint there. Else, what's the full stacktrace? – Larme Apr 20 '21 at 07:30

1 Answers1

1

Your code boils down to

- (void)viewDidLoad {
    // main queue
    [super viewDidLoad];
    [weakSelf.activityIndicator startAnimating];
    // still main queue
    dispatch_async(dispatch_get_main_queue(), ^{
        // main queue
        [[GIFMIAPHelper defaultHelper] requestProductsWithCompletionHandler:^(BOOL success, SKProduct *productPro, NSError *error) {
            // completion handler, not on a specific thread
            [weakSelf.activityIndicator stopAnimating];
        }];
    });
}

Move dispatch_async(dispatch_get_main_queue() to the completion handler:

- (void)viewDidLoad {
    // main queue
    [super viewDidLoad];
    [weakSelf.activityIndicator startAnimating];
    // still main queue
    [[GIFMIAPHelper defaultHelper] requestProductsWithCompletionHandler:^(BOOL success, SKProduct *productPro, NSError *error) {
        // completion handler, not on a specific thread
        dispatch_async(dispatch_get_main_queue(), ^{
            // main queue
            [weakSelf.activityIndicator stopAnimating];
        });
    }];
}
Willeke
  • 14,578
  • 4
  • 19
  • 47