Using Main Thread Checker (MTC) for UI testing & rendering
Identify main thread issues while running UI Tests using the Main Thread Checker tool.
UI rendering is a complex and expensive operation. It requires synchronization and concurrency of so many objects and their states that it becomes necessary, from a performance standpoint, to make sure operations get executed from a single queue only.
Quoting from this great article on Thread-Safe Class Design by author Peter Steinberger on why UIKit is not inherently thread safe:
“It’s a conscious design decision from Apple’s side to not have UIKit be thread-safe. Making it thread-safe wouldn’t buy you much in terms of performance; it would in fact make many things slower. And the fact that UIKit is tied to the main thread makes it very easy to write concurrent programs and use UIKit. All you have to do is make sure that calls into UIKit are always made on the main thread.”
Hence, UIKit (and AppKit) APIs must be always called from the main thread. Otherwise, there is a high probability that your application will behave unexpectedly.
Safeguarding against main thread violations
Let's say you want to add a simple way to execute code on the main thread in your codebase. It would roughly be written this way wherever you are calling a UIKit API.
For obvious reasons, this would become burdensome very quickly, and significantly bloat your codebase. Fortunately, Apple has a Main Thread Checker (MTC) tool that comes in handy.
What is the Main Thread Checker?
The Main Thread Checker (MTC) is a runtime tool that throws a warning when system API calls that should be made on the main thread, such as UI operations, are incorrectly called on a background thread.
Enabling MTC
Enabling MTC for your app is very simple. Just toggle on the Main Thread Checker in the Diagnostic tab.
From this point on, whenever you run your app and a main thread violation occurs, the control flow will hit a breakpoint letting you know that a violation has occurred.
Using MTC in UI tests
Turning on MTC while running a UI test can enable your UI tests to detect these violations for you, since MTC violations can only be detected when you execute a specific code path during runtime.
Enabling MTC for your UI tests can be as simple as toggling on the Main Thread Checker option in your app’s scheme. Alternatively, you can do it programmatically with a couple of lines of code.
UI tests residing in the same project or workspace
If your UI test resides in the same workspace as your app, then you can simply check the checkbox for Main Thread Checker in your app’s scheme as shown below:
When the UI tests are executed, Xcode will compile your app and launch it with the MTC enabled.
UI tests residing in a different project or workspace
If your UI tests live in a separate project or workspace, then the above approach will not work. Instead, tests in this configuration will not use the scheme settings of your app to launch your app.
To enable the MTC in this scenario, we would need to inject our MTC enable flag while launching the app from the UI Test workspace.
Going through Apple Docs gives us some idea of how Xcode enables MTC:
Because Main Thread Checker doesn’t require you to recompile your code, you can run it on an existing macOS binary.
Inject the dynamic library located at /Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib
into your executable.
So we can enable MTC by finding the correct libMainThreadChecker.dylib for the architecture and then injecting it in while launching the app.The path to dylib above (from Apple’s documentation) is for macOS. For different versions/OS of iOS Simulators, we would need to set the path to the dylib dynamically.
Finding the right path to the libMainThreadChecker.dylib
At launch time, the OS injects DYLD_ROOT_PATH
as part of the environment variables. This can be used to find the relevant mainThreadChecker.dylib.
Combining the above two we can inject MTC dylib while launching the SUT app from the UI Tests like:
(The above will not occur if your app is not attached to the debugger, which is the case when the UI Tests and your app project are in different workspaces.)
A useful technique to deal with this scenario is to crash your app when such a violation occurs. To achieve that simply pass MTC_CRASH_ON_REPORT
as ON to the environment variables.
Conclusion
UIKit APIs are not inherently thread-safe, which adds an additional burden on developers to sanitize all UIKit API call sites. The problem is especially exacerbated in large codebases where it is not uncommon to have several asynchronous callbacks. The Main Thread Checker tool can help mitigate some of that risk. Understanding how to use the MTC will allow you to use the UIKit APIs more effectively.
Technology vector created by stories - www.freepik.com