0Pricing
Swift Academy · Lesson

Main Thread Checker and Hangs

Detecting UI work on background threads and resolving main-thread hangs.

Main Thread Checker and Hangs is a free Swift Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Main Thread Checker

The Main Thread Checker (MTC) is a Xcode diagnostic that reports UIKit/AppKit API calls made from background threads at runtime.

// Enabled by default in Xcode Debug scheme:
// Product → Scheme → Edit → Diagnostics → ☑ Main Thread Checker
// Violating calls log a purple runtime warning in Xcode

Common MTC Violations

Updating UI from completion handlers or background queues are the most frequent violations.

URLSession.shared.dataTask(with: url) { data, _, _ in
  self.label.text = "Done"  // VIOLATION: background thread
}.resume()
// Fix:
DispatchQueue.main.async { self.label.text = "Done" }

MTC with async/await

Marking a class or function with @MainActor guarantees all its methods run on the main thread.

@MainActor
final class ProfileViewModel: ObservableObject {
  @Published var name = ""
  func load() async {
    name = try? await fetchName()  // always on main thread
  }
}

What Are Hangs?

A hang is when the main thread is blocked for too long, making the app unresponsive. iOS watchdog terminates apps that hang at launch (>20s) or after a user action.

// Common causes:
// - Synchronous network calls on main thread
// - Heavy computation blocking main run loop
// - Deadlocks between DispatchQueues

Instruments Hangs Template

The Hangs instrument in Xcode 14+ records hang events with full stack traces to diagnose main thread blockage.

// Instruments → Hangs template
// Shows hang duration (ms), call stack, and responsible frame
// Look for synchronous I/O or heavy loops

Main Thread Checker in Tests

MTC also fires during UI tests. Configure it in the test scheme to catch violations in automated UI flows.

// XCTestScheme Diagnostics → ☑ Main Thread Checker
// UI test violation = instant fail in CI

MetricKit Hang Reports

MetricKit collects hang reports from production apps in MXHangDiagnosticPayload.

func didReceive(_ payloads: [MXDiagnosticPayload]) {
  payloads.compactMap { $0.hangDiagnostics }.flatMap { $0 }.forEach {
    print($0.callStackTree)
  }
}

Detecting Hangs with Instruments

Record a trace and look for time intervals where the Main Thread is executing but the display link isn't firing (dropped frames).

// Time Profiler: look for long CPU bursts on main thread
// Core Animation: FPS drops to 0 during hang
// Hangs instrument: highlights exact frames where hang occurred

Async Fixes for Common Hangs

Move CPU-heavy work off the main thread using Task.detached or async let.

// HANG:
let processed = heavyProcessing(image)  // blocks main thread
// FIX:
let processed = await Task.detached(priority: .userInitiated) {
  heavyProcessing(image)
}.value

Avoiding Synchronous APIs on Main Thread

Replace synchronous file I/O and network calls with async equivalents or move them to a background queue.

// HANG: synchronous file read on main thread
let data = try! Data(contentsOf: largeFileURL)
// FIX:
let data = try await Task.detached { try Data(contentsOf: largeFileURL) }.value

Main Run Loop and Timers

Long-running operations on the main run loop (in RunLoop.main callbacks) also cause hangs. Move computation off the main loop.

// AVOID: heavy work in main-thread timer callback
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
  processAllItems()  // slow!
}
// PREFER: dispatch to background
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
  Task.detached { processAllItems() }
}

Quick Check

What does adding @MainActor to a class guarantee about its method calls?

Lesson Recap

Enable Main Thread Checker in Debug scheme to catch UI-from-background violations. Use @MainActor on ViewModels and views. Move heavy work off the main thread with Task.detached or background queues. Use the Hangs instrument and MetricKit MXHangDiagnosticPayload to diagnose real-world hangs.

Frequently asked questions

Is the “Main Thread Checker and Hangs” lesson free?

Yes — the full text of “Main Thread Checker and Hangs” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Main Thread Checker and Hangs”?

Detecting UI work on background threads and resolving main-thread hangs. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Main Thread Checker and Hangs” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Instruments Basics: Time Profiler
  2. Allocations and Leaks Instruments
  3. Main Thread Checker and Hangs
  4. Optimising Hot Paths: COW, Inlining and Specialisation
← Back to Swift Academy