BadCodeDeveloper
BadCodeDeveloper

Reputation: 475

iOS What should I call from the main thread when changing UI?

I have an extension for alert presentation. When showAlert() called not from the main thread, I get "changing UI not from the main thread" behaviour. Sometimes an alert not presented at all, sometimes with big delay. No crashes. But if I put all the function's code inside the DispatchQueue.main.async closure - everything is fine. Why? Shouldn't I call from the main thread only the code that actually changes UI?

@objc extension UIAlertController {

static func showAlert(title: String?, message: String? = nil, closeActionTitle: String? = "OK", preferredStyle: UIAlertControllerStyle = .alert, actions: [UIAlertAction]? = nil) {
        let alertController = UIAlertController(title: title,
                                                message: message,
                                                preferredStyle: preferredStyle)
        var allActions = [UIAlertAction]()
        if let closeTitle = closeActionTitle {
            allActions.append(UIAlertAction(title: closeTitle, style: .cancel))
        }
        allActions.append(contentsOf: actions ?? [])
        allActions.forEach { alertController.addAction($0) }

        let vc = ClearViewController()
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = vc
        window.backgroundColor = AppTheme.color.clear
        window.windowLevel = UIWindowLevelAlert

    DispatchQueue.main.async {
        window.makeKeyAndVisible()
        vc.present(alertController, animated: true)
    }
}

}

Upvotes: 0

Views: 1431

Answers (3)

Duncan C
Duncan C

Reputation: 131426

Short answer: UIKit is not thread-safe. You should make ALL calls to UIKit, including creating or setting properties on UIKit objects, from the main thread. There are a few minor exceptions to this, but only a few and they are usually well documented. Interacting with UIKit objects from the main thread is never bad, and interacting with UIKit objects from background threads is almost always bad. (Results of this are "undefined" and sometimes fatal.)

If you do ALL interaction with UIKit objects from the main thread you won't have any concurrency problems. Do your time-consuming networking and/or number crunching code in the background, and then wrap the code to present the results to the user in calls to the main thread.

Upvotes: 4

Thomas
Thomas

Reputation: 2063

You are correct, all UI changing operations should be done on the main thread only. If not, the system does not assure you when, in which order of even if the UI is going to be updated with your code.

Now, you are right to want to clutter the main thread as little as possible and put only the UI related code. But if you look closer at the lines you will notice these:

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = vc

which are modifying the UI also but are outside of the main closure. I believe that if you move these 2 lines on the main thread, your warning will be gone!

Upvotes: 2

Jogendar Choudhary
Jogendar Choudhary

Reputation: 3494

May be you are calling show alert method from background So you are invoking main thread with this code

DispatchQueue.main.async {
    window.makeKeyAndVisible()
    vc.present(alertController, animated: true)
}

All UI changes should be in main thread.

Upvotes: 0

Related Questions