Introduction

The Main Thread Checker (MTC) was introduced in Xcode 9. Its goal is to simple: to detect improper use of APIs on a background thread. Updating using a background thread can cause unknown bugs, crashes and strange UI behavior that can easily be avoided.

Updating UI from the background thread

One of the most common mistakes is updating the UI from a background thread. Often this is done when using a completion handler to fetch data.

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
       if let data = data {      
          self.label.text = "\(data.count) bytes downloaded" 
       }
    }
    task.resume()

The above example would result in the MTC returning an error. If you turn on the “Pauses on Issues” option, when the code is run and attached to the debugger it will pause when it hits the above section. This is particularly useful in finding your errors and solving them. The MTC will also generate a UIApplication warning. It is easily solved by dispatching the main thread.

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
      if let data = data {
        DispatchQueue.main.async {      
          self.label.text = "\(data.count) bytes downloaded" 
        }
      }
    }
    task.resume()

Turning on main thread checker

  1. Open your Xcode Project.
  2. Edit your scheme.
  3. Select your run scheme.
  4. Select the Diagnostics tab.
  5. Check the Main Thread Checker tick box that appears within the Diagnostics tab
  6. Check the Pauses on issues tick box. (Optional)

You can also repeat the above for the test scheme.

Turn on main thread checker

Performance

Turning on the MTC has very little overhead. The CPU overhead sits at only 1-2% and additional launch time of less than 0.1s. The low overhead means that Xcode automatically enables the main thread checker. It does not turn on the Pause on issues.

The MTC has much better performance compared with the thread sanitizer. The thread sanitizer is used to check for race conditions. The thread sanitizer can use up to 10x more memory and 20x more CPU usage. The MTC clearly has low overhead when compared to other diagnostic tools and shouldn’t be turned off for performance related issues.

How it works

The MTC does not have to recompile code like other diagnostic tools meaning it can be used against existing binaries. You can also run the MTC on a continuous integration system by injecting binaries into:

    /Applications/Xcode.app/Contents/Developer/usr/lib/libMainThreadChecker.dylib

The MTC will not check against methods that are known to be safe on background threads, only those that should be run on the main thread.

Conclusion

The MTC has an extremely low overhead, meaning that it won’t add much if any time to your development cycle. Working from a background thread can cause undesired behavior that is unpredictable and hard to debug. Using the MTC will allow you to catch these errors before they go into production.