Reputation: 475
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
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
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
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