iOS path · The running app · Topic 2 of 11

The main thread and run loop

Not started

Every iOS app starts with one thread, the main thread, and the frameworks set up a **run loop** on it before your first line of code runs. The loop waits for something to happen (a touch, a timer, a message from another thread), runs the handler for it, and then gives the rendering system a chance to commit the changes you made. UIKit and SwiftUI are built on the assumption that they only ever run on this thread.

Why it matters: Anything slow on the main thread freezes touches and animations. Past about 100 ms, people notice; long enough, and the system ends the app.

  1. 1One loop

    The main thread runs a run loop that sleeps until an event arrives, handles it, then lets the frame be drawn.

  2. 2UI only there

    UIKit and SwiftUI state must be touched on the main thread (the main actor in Swift concurrency).

  3. 3Budget per frame

    At 60 Hz a frame lasts 16.7 ms and at 120 Hz just 8.3 ms, shared with everything else the main thread does.

1

How it works

The explanation, with examples you can run.

One turn of the loop

You can picture the main thread doing this, forever:

  1. Sleep until an input source or timer fires.
  2. Call your code: a gesture handler, a body evaluation, a timer callback, a block queued with DispatchQueue.main.async, a main-actor task resuming.
  3. Lay out and commit any UI changes, so the next frame can show them.

While step 2 runs, steps 1 and 3 can't. A slow handler doesn't just delay itself: it delays every touch and every frame after it. Apple's responsiveness guide puts a discrete delay above 100 ms in "hang" territory, and suggests keeping the main thread's work for a frame under about 5 ms, since a 120 Hz display gives you 8.3 ms in total.

Keep the UI on main, the rest off it

UI objects aren't thread-safe. Changing a view or a value SwiftUI observes from a background thread can corrupt state or crash, and Xcode's Main Thread Checker reports such calls while you debug. The pattern is: do heavy work elsewhere, then come back to the main thread to show the result.

@MainActorfinal class PhotoViewModel {    var thumbnail: UIImage?     func load(file: URL) async {        let image = await Self.makeThumbnail(file)   // runs on the thread pool        thumbnail = image                            // back on the main actor    }     // @concurrent (Swift 6.2) means "run on the thread pool, not on the caller's actor".    // In Swift 6.0 and 6.1, write nonisolated instead: that already ran async code off the actor.    @concurrent static func makeThumbnail(_ file: URL) async -> UIImage? {        guard let data = try? Data(contentsOf: file) else { return nil }        return UIImage(data: data)?.preparingThumbnail(of: CGSize(width: 200, height: 200))    }}

With Swift concurrency, "the main thread" is spelled @MainActor, and the compiler checks it for you; with Grand Central Dispatch it is DispatchQueue.main. The idea is the same. One caution from Apple's guide: hopping back to the main thread costs something too, so don't schedule work there that doesn't need to be there, not even asynchronously.

Run loop modes

A run loop runs in a mode that decides which sources it listens to. While a scroll view is tracking a finger, the main run loop switches to a tracking mode, and a Timer scheduled in the default mode stops firing until the scroll ends. Adding the timer to the common modes keeps it running:

let timer = Timer(timeInterval: 1, repeats: true) { _ in updateClock() }RunLoop.main.add(timer, forMode: .common)

You rarely manage run loops yourself (secondary threads only have one if you run it), but knowing the main thread is a loop explains most "why didn't my UI update" and "why did it freeze" questions.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.A button handler reads and decodes a 20 MB JSON file before updating a label. What does the user see?
  2. 2.Which tool tells you at runtime that a UIKit API was called off the main thread?
  3. 3.What is the relationship between the main actor and the main thread?

0 of 3 right so far. Get all three to finish this topic.