Why are UI events not thread-safe in Swift/Objective-C?

903 views Asked by At

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?

2

There are 2 answers

2
Ralfonso On BEST ANSWER

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).

0
ipmcc On

Making things thread-safe is difficult, and has major performance implications, all of which can be avoided by requiring access to come from a single (main) thread. You want your UI frameworks to be as fast (i.e. responsive) as possible. If you declare that all your UI objects are main-thread only, then you don't have to slow them down with a bunch of synchronization overhead. I've heard it asked, 'if UIKit is really main-thread only, then why don't they just check the current thread and abort() if it's not the main thread?' and the answer is the same: Even a simple check like that would result in an appreciable performance hit given the number of methods that would need the check and the frequency with which they're called. It's all about speed.

Also, remember, not too long ago, all applications were single threaded.