So I'm beginning to learn the basics of Grand Central Dispatch and the whole concept of multithreading with iOS applications. Every tutorial will tell you that you must run UI events on the main thread, but I don't completely understand why.
Here's a problem I came across yesterday, and finally fixed it by running a segue on the main thread, but I still don't understand why running it off the main thread was a problem:
I had a custom initial VC (barcode scanner) and a segue to a new view controller with a UIWebView
attached. As soon as the VC found a barcode, it called a handler, and in that closure, I had a performSegueWithIdentifier
. However, I got a EXC_BAD_ACCESS
because of this (it didn't happen when the second VC had a label or a UIImageView
, just with UIWebView
). I finally realized that for some reason, the closure was called off the main thread, and thus the segue was being performed off the main thread. Why exactly would performing the segue on another thread throw a memory error? Is it because self
in self.performSegueWithIdentifier
was somehow nil? And why wouldn't Swift automatically dispatch a segue event on the main thread?
Interesting question! The crash isn't related to UIKit. It's a crash specific to UIWebView. Looking at the stack trace, the exception happens in the
WebCore::FloatingPointEnvironment::saveMainThreadEnvironment
function, which is part of the WebKit init process. Since WebKit manages a threaded execution environment of its own, it makes sense that it needs a definite starting point (i.e. the main thread) to build this environment.UIKit operations (like presenting a view controller) performed on threads other than
main
will not cause an exception, but they will be delayed (depending on the QoS of the dispatching queue).As for why the UIKit operations aren't automatically dispatched on the main queue, I can only speculate that adding extra checks inside the library calls would add too much redundant work that can be avoided simply by following a convention.
For a larger discussion on UIKit and the main thread, see this answer: Why must UIKit operations be performed on the main thread?
The short answer is that all operations that modify the UI of your app need to come together in one place to be evaluated to generate the next frame at regular intervals (the V-Sync interval specifically). Keeping track of all of the mutated state requires all changes to happen synchronously, and for performance reasons, all of these operations are generally batched up and executed once per frame (while also coordinating with the GPU).