iOS
threading
UI API
UIApplication
multithreading

Main Thread Checker UI API called on a background thread -UIApplication applicationState

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This warning means code touched a UI-related API from a background thread. In this specific case, UIApplication.shared.applicationState was read off the main thread, and Main Thread Checker is correctly warning that UIKit access must stay on the main queue.

Why applicationState Counts as UI API

Even though applicationState looks like a simple property read, it belongs to UIApplication, which is part of UIKit. UIKit is not thread-safe in the general sense expected by background worker code, so reads and writes that involve application state, view state, and other UI objects are supposed to happen on the main thread.

A common bad pattern looks like this:

swift
1DispatchQueue.global().async {
2    let state = UIApplication.shared.applicationState
3    print(state)
4}

This may appear harmless, but it violates the threading rules that Main Thread Checker is designed to catch.

Fix It by Hopping Back to the Main Queue

If a background task needs UI-related information, fetch that information on the main queue.

swift
1DispatchQueue.global().async {
2    // background work
3
4    DispatchQueue.main.async {
5        let state = UIApplication.shared.applicationState
6        print(state)
7    }
8}

That is the simplest fix when the background work genuinely needs to consult UIKit before updating the UI or making a UI-dependent decision.

Better Design: Separate Background Logic From UI Logic

The stronger fix is often architectural rather than mechanical. Background work should usually produce data, not reach directly into UIKit. Then the UI layer can consume that data on the main thread.

swift
1func refreshData(completion: @escaping (Result<String, Error>) -> Void) {
2    DispatchQueue.global(qos: .userInitiated).async {
3        let result: Result<String, Error> = .success("done")
4        DispatchQueue.main.async {
5            completion(result)
6        }
7    }
8}
9
10refreshData { result in
11    let state = UIApplication.shared.applicationState
12    print(state, result)
13}

This keeps UIKit access in the main-thread callback, where it belongs.

Use MainActor in Modern Swift When Appropriate

In newer Swift code, @MainActor can make UI-thread intent explicit.

swift
1import UIKit
2
3@MainActor
4func logApplicationState() {
5    print(UIApplication.shared.applicationState)
6}

If asynchronous code needs to call this from a concurrent context, Swift's actor isolation helps route the work back to the main actor correctly.

That does not replace understanding the thread model, but it does make the safe path easier to express.

Debug the Real Call Site, Not Just the Symptom

When Main Thread Checker fires, the important question is not only where the warning appeared, but why background code had access to UIKit at all.

Typical sources include:

  • network completion handlers updating UI directly
  • notification callbacks running on non-main queues
  • custom operation queues touching UIKit
  • reactive pipelines delivering values off the main thread

If you only wrap the one property read in DispatchQueue.main.async, the warning may go away while the broader design problem remains.

A Useful Rule of Thumb

Keep these boundaries clear:

  • background queues do computation, I/O, and parsing
  • the main queue reads or mutates UIKit state
  • data crosses from background work to UI through an explicit handoff

Once the code is structured that way, Main Thread Checker warnings become much easier to understand and eliminate.

Common Pitfalls

The most common mistake is assuming that a read-only UIKit property is safe on any thread because no mutation occurs.

Another common issue is dispatching some UI updates to the main queue while leaving related UIKit reads on background queues. Developers also often treat the warning as a one-line fix instead of tracing the flow that allowed background code to depend on UI state in the first place.

Summary

  • 'UIApplication.shared.applicationState is a UIKit API and should be accessed on the main thread.'
  • Main Thread Checker is warning about a real threading violation.
  • The immediate fix is to hop back to DispatchQueue.main before touching UIKit.
  • The better long-term fix is to separate background work from UI logic.
  • In modern Swift, @MainActor can help make main-thread UI access explicit.

Course illustration
Course illustration

All Rights Reserved.