Terminating app due to uncaught exception 'NSInternalInconsistencyException'

Hi,

After ios 13 release I retested my app and I am getting an error which was not on previous versions of ios:


Main Thread Checker: UI API called on a background thread: -[UIPageViewController setViewControllers:direction:animated:completion:]

and

Main Thread Checker: UI API called on a background thread: -[UIPageViewController setViewControllers:direction:animated:completion:]

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 the code:

@IBAction func nextButtonTapped(_ sender: UIButton) {

let onboardingPager = self.parent as! OnboardingPager



var purchased = Bool(false)

DoctorSugarProducts.store.requestProducts{ [weak self] success, products in

guard let self = self else { return }

if success {

//self.products = products!

for product in products! {

if DoctorSugarProducts.store.isProductPurchased(product.productIdentifier) {

purchased = true

}

}

if purchased == false {

//onboardingPager.transition(from: onboardingPager.getStepZero(), to: onboardingPager.getStepOne(), duration: 0, options: [], animations: nil, completion: nil)

onboardingPager.setViewControllers([onboardingPager.getStepOne()], direction: .forward, animated: true, completion: nil)

} else {

onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)

}

} else {

onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)

}

}


}


Line causing the problem is the last line: onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)


Products requested are products from SKProductsRequestDelegate.


Does anyone have similar problem and knows what to do about it?

Accepted Reply

I have not experienced exactly the same issue, but the fix seems to be clear.


Do all the UI API calls and possibly thread-unsafe operations on the main thread.


Something like this:

    @IBAction func nextButtonTapped(_ sender: UIButton) {
        let onboardingPager = self.parent as! OnboardingPager
        
        DoctorSugarProducts.store.requestProducts{ success, products in
            DispatchQueue.main.async {
                if success {
                    let purchased = (products ?? []).contains {
                        DoctorSugarProducts.store.isProductPurchased($0.productIdentifier)
                    }
                    
                    if !purchased {
                        onboardingPager.setViewControllers([onboardingPager.getStepOne()], direction: .forward, animated: true, completion: nil)
                    } else {
                        onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)
                    }
                } else {
                    onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)
                }
            }
        }
    }

Some changes other that putting `DispatchQueue.main.async {...}` are just for my preference, but I believe you have no need to use `[weak self]` in your case.

Replies

I have not experienced exactly the same issue, but the fix seems to be clear.


Do all the UI API calls and possibly thread-unsafe operations on the main thread.


Something like this:

    @IBAction func nextButtonTapped(_ sender: UIButton) {
        let onboardingPager = self.parent as! OnboardingPager
        
        DoctorSugarProducts.store.requestProducts{ success, products in
            DispatchQueue.main.async {
                if success {
                    let purchased = (products ?? []).contains {
                        DoctorSugarProducts.store.isProductPurchased($0.productIdentifier)
                    }
                    
                    if !purchased {
                        onboardingPager.setViewControllers([onboardingPager.getStepOne()], direction: .forward, animated: true, completion: nil)
                    } else {
                        onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)
                    }
                } else {
                    onboardingPager.setViewControllers([onboardingPager.getStepTwo()], direction: .forward, animated: true, completion: nil)
                }
            }
        }
    }

Some changes other that putting `DispatchQueue.main.async {...}` are just for my preference, but I believe you have no need to use `[weak self]` in your case.