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)
}
}
}
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:
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!