Heavy work must stay off the main thread. Modern Swift uses async/await and actors; the classic approach is Grand Central Dispatch (GCD).
async/await
func loadAll() async -> [Item] {
async let a = fetchPage(1)
async let b = fetchPage(2)
return await a + b // both run in parallel
}
GCD (classic)
DispatchQueue.global(qos: .userInitiated).async {
let result = heavyWork()
DispatchQueue.main.async { self.updateUI(result) }
}
@MainActor
Annotate UI-touching code with @MainActor so it always runs on the main thread.
Summary
Prefer async/await for new code; understand GCD for older code. Always hop back to the main thread for UI.