Swift 6.2 Finally Made Concurrency Approachable — Someone Already Built a Parody Site
There’s a scene in Office Space where Peter Gibbons explains to the consultants that he has eight different bosses. Every time he makes a mistake, eight different people come by to tell him about it. “That’s my only real motivation — not to be hassled.”
That’s what Swift strict concurrency has felt like since 5.10.
You write a perfectly reasonable async function. The compiler throws six warnings at you. You add @Sendable. Now it wants @MainActor. You add that. Now something else isn’t Sendable. You make it Sendable. Now a downstream closure is upset. You’ve been at this for 45 minutes and the function still does the same thing it did before — fetch some JSON and show it on screen.
Swift 6.2 just walked into the room and said: “Okay. We hear you. That was too hard.”
The Problem Nobody Wanted to Admit
Swift’s concurrency model — introduced in Swift 5.5 with async/await — was a genuine leap forward. The actor model, structured concurrency, Sendable checking — all brilliant on paper. The kind of thing that makes language designers nod approvingly at conferences.
Then real developers tried to migrate their apps.
The experience was, to put it charitably, educational. To put it honestly: a bloodbath of compiler warnings that nobody understood, escape hatches that felt like giving up, and a general sense that the language was punishing you for writing code that had worked fine for years.
Antoine van der Lee at SwiftLee described the pre-6.2 era as requiring developers to “understand all actors and their behaviors” before writing even basic async code. Paul Hudson at Hacking with Swift documented the sheer volume of annotation gymnastics developers had to perform.
And then someone built fuckingapproachableswiftconcurrency.com. Because of course they did.
What Actually Changed in Swift 6.2
The core philosophy flipped. Instead of “everything runs everywhere unless you annotate it,” Swift 6.2 says: “everything runs on the main actor unless you say otherwise.”
That’s it. That’s the tweet. But the implications are massive.
@MainActor by Default
This is the headline change, formalized in SE-0461. In the new upcoming language mode, your code defaults to @MainActor isolation. Functions, closures, types — they all run on the main actor unless you explicitly tell them not to.
Before Swift 6.2:
// You had to annotate everything manually
@MainActor
class ProfileViewModel: ObservableObject {
@MainActor @Published var user: User?
@MainActor
func loadProfile() async {
// Where does this run? Depends on context.
// Add @MainActor. Wait, the network call shouldn't be on main.
// Add nonisolated. No wait, @Sendable. Actually...
let data = try await api.fetchUser()
self.user = data // Warning: might not be on main actor
}
}
After Swift 6.2:
// Everything is @MainActor by default. No annotation needed.
class ProfileViewModel: ObservableObject {
@Published var user: User?
func loadProfile() async throws {
let data = try await api.fetchUser()
self.user = data // Guaranteed main actor. No warnings. Just works.
}
}
The compiler finally assumes what 90% of app developers wanted all along: UI code runs on the main thread.
The New @concurrent Attribute
But what about background work? That’s where @concurrent comes in (SE-0466). It’s the explicit opt-out. You slap @concurrent on a function and it runs off the main actor:
@concurrent
func processImage(_ data: Data) async -> UIImage {
// Heavy work, explicitly on a background thread
let processed = await applyFilters(data)
return processed
}
This is the opposite of the old model. Before: everything was implicitly concurrent, and you annotated to restrict. Now: everything is implicitly main-actor, and you annotate to release.
It’s like moving from “every door is unlocked until you lock it” to “every door is locked until you unlock it.” Which is, you know, how actual building security works.
nonisolated Gets Saner
The third piece is SE-0462. In the new model, nonisolated functions default to nonisolated(nonsending) — they inherit the caller’s isolation context instead of being free-floating. If a main-actor function calls a nonisolated function, it stays on the main actor. No surprise thread hops.
This kills a whole category of bugs where nonisolated functions would silently jump to a random executor and cause subtle data races that only manifested on users’ devices at 3 AM on a Sunday.
The Migration: Less Pain Than You’d Expect
If you’ve been dreading another round of concurrency annotation whack-a-mole, take a breath. The migration path is genuinely gentler than the 5.10 → 6.0 transition.
Step 1: Update to Xcode with Swift 6.2 support. Make sure you’re on the latest toolchain.
Step 2: Enable the upcoming feature flag in your build settings:
// In your Package.swift
.swiftLanguageMode(.v6),
.enableUpcomingFeature("ApproachableConcurrency")
Step 3: Remove redundant @MainActor annotations. If your view model was annotated @MainActor and all its methods were too — strip most of that. The compiler now infers it.
Step 4: Add @concurrent to functions that genuinely need background execution. Image processing, heavy computation, network parsing — anything that shouldn’t block the main thread.
Step 5: Run your tests. The implicit isolation changes mean some code that was accidentally running on background threads is now correctly running on main. If you had race conditions that “worked” because of timing, they might surface differently now.
If you’ve been following our SwiftUI courses, the patterns we teach already lean toward main-actor-first design. This migration should feel natural.
The Community Is… Divided
You’d think “we made your code simpler” would be universally celebrated. You’d be wrong. This is the Swift community. We argue about tab widths.
The celebration camp says it’s about time. Most app code runs on the main thread anyway. Making that the default removes ceremony from the 90% case and forces developers to think only when they actually need concurrency. It’s the right tradeoff.
The skeptic camp worries about hidden main-thread bottlenecks. If everything runs on @MainActor by default, junior developers might never learn why they should move work off the main thread. Blazej Sleboda published “Swift Concurrency is a Mess in 2026” arguing that the constant back-and-forth changes have eroded trust in the concurrency model itself.
Both sides have a point. But here’s mine: a model that’s simple enough to use wrong is better than a model that’s too complex to use at all. Most developers weren’t using strict concurrency correctly before — they were slapping @unchecked Sendable and nonisolated(unsafe) on everything to make the warnings disappear. At least now the default behavior is actually safe.
Bonus: Two Features That Punch Above Their Weight
Swift 6.2 also shipped two features that don’t get enough attention:
InlineArray — A fixed-size array stored entirely on the stack. No heap allocation. No ARC overhead. If you’re writing performance-sensitive code — games, audio processing, on-device ML inference — this matters:
let buffer: InlineArray<8, Float> = [0, 0, 0, 0, 0, 0, 0, 0]
// Lives on the stack. No heap. No ARC. Fast.
Span — A safe, non-copyable view into contiguous memory. Think of it as UnsafeBufferPointer but with compile-time safety guarantees. You get C-level performance without C-level foot-guns.
Both features signal that Swift is serious about systems-level performance — important context as Swift expands to Android and WebAssembly targets.
What Should You Do Right Now?
Starting a new project? Use Swift 6.2 with ApproachableConcurrency enabled from day one. You’ll write less boilerplate and the defaults will do the right thing.
Maintaining an existing app? Don’t rush. The old concurrency model still works. But start experimenting in a feature branch. Remove some @MainActor annotations, add @concurrent where needed, and see how it feels.
Preparing for WWDC 2026? This is likely the direction Apple will double down on in June. Expect Xcode 27 and iOS 27 to lean heavily into the new concurrency defaults. We wrote about what else developers should prepare for WWDC — add concurrency migration to that list.
Tried strict concurrency before and gave up? Give it another shot. The annotations that made you question your career choices are mostly gone. The compiler is working with you now, not against you. If you need a structured path, our SwiftUI courses cover modern concurrency patterns that align perfectly with where 6.2 is heading.
The Bigger Picture
Swift’s concurrency story has been a bumpy road. The vision was always sound — memory safety, data-race freedom, structured concurrency. But the execution asked too much of working developers who just wanted to fetch some data and put it on screen.
Swift 6.2 is the course correction: same safety guarantees, dramatically less friction. The @MainActor-by-default philosophy matches how most apps actually work. And @concurrent gives you a clean, explicit escape hatch when you need real parallelism.
Is it perfect? No. The “upcoming feature flag” dance means you’re still opting into behavior that should arguably just be the default. The community trust has been dented by years of churn. And we’re probably one WWDC away from yet another concurrency refinement.
But for the first time since async/await landed in 2021, writing concurrent Swift code feels like writing Swift code — not like filing a tax return in a language you don’t speak.
And hey, at least we got a great parody site out of it.
Related reading:
Share this post
Comments
Leave a comment
NativeFirst Team
EditorialThe NativeFirst team — engineers and designers building native Apple apps and writing the courses we wish we had when we started.