Swift 6.2's Approachable Concurrency: What Actually Changes

Swift Concurrency has been available for years, but many developers still avoid it. Swift 6.2's Approachable Concurrency changes that—here's what it means for your code.

Remember the first time you enabled strict concurrency checking in Swift 6? I do. My project went from zero warnings to what felt like a thousand. Sendable this, actor isolation that, nonisolated functions running on mysterious executors. I turned it off and pretended I never saw any of it.

If that sounds familiar, Swift 6.2’s Approachable Concurrency is for you. And me. And probably most of us who’ve been quietly ignoring concurrency warnings.

What’s the Big Idea?

Approachable Concurrency isn’t just marketing speak—it’s an actual build setting in Xcode. The core philosophy is progressive disclosure: Swift should only ask you to understand as much concurrency as you actually use.

Most apps don’t need parallelism. They need to call some async APIs, maybe fetch data from a server, update the UI. That’s it. But pre-6.2, the compiler treated every project like it was building a high-performance compute cluster. Even simple code triggered warnings about sendability and actor isolation.

Swift 6.2 flips the script. Your code starts in a single-threaded, main-actor-by-default world. You write sequential code like you always have. Only when you intentionally introduce parallelism do you need to think about all the actor and sendability stuff.

The Two Features That Matter

Under the hood, Approachable Concurrency enables two key changes. Understanding them helps you know what your code is actually doing.

1. nonisolated(nonsending) by Default

This one’s a mouthful, but it solves a genuine pain point.

In Swift 6.1 and earlier, when you called a nonisolated async function from the main actor, it would hop to a background thread. Even if it didn’t need to. Even if you were just awaiting some data and updating a property.

// Swift 6.1 behavior - this runs on a background thread!
func loadUserData() async -> User {
    // Even though there's no CPU-intensive work here,
    // this function runs off the main actor
    return await apiClient.fetchCurrentUser()
}

This was confusing and often wrong. If you called this from a @MainActor context, you’d need to hop back to update UI—and if you forgot, you’d get a data race.

Swift 6.2 changes the default. Now, nonisolated async functions inherit the caller’s isolation. If you call them from the main actor, they run on the main actor:

// Swift 6.2 behavior - runs on the caller's actor
func loadUserData() async -> User {
    // This now runs on whatever actor called it
    return await apiClient.fetchCurrentUser()
}

No surprise thread hops. The code does what it looks like it does.

2. What About Background Work?

“But wait,” you’re thinking, “sometimes I want to run things in the background.” Absolutely. That’s what @concurrent is for.

When you have genuinely CPU-intensive work—decoding a large JSON blob, processing an image, crunching numbers—you mark the function @concurrent:

@concurrent
func processImage(_ data: Data) async -> UIImage? {
    // Heavy work that should NOT block the main actor
    let processed = applyFilters(to: data)
    return UIImage(data: processed)
}

This tells Swift: “Yes, I know what I’m doing. Run this off the caller’s actor.”

The mental model becomes much clearer:

  • Default (nonisolated): Runs on the caller’s actor. Safe, predictable.
  • @concurrent: Explicitly opts into background execution. Use for heavy work.

3. Inferred Isolated Conformances

The second feature (SE-0470) solves a more subtle problem. Ever tried to make a @MainActor class conform to Equatable and gotten weird errors?

@MainActor
class UserSettings: Equatable {
    var theme: Theme
    var fontSize: Int

    // Pre-6.2: This was a pain to get right
    static func == (lhs: UserSettings, rhs: UserSettings) -> Bool {
        lhs.theme == rhs.theme && lhs.fontSize == rhs.fontSize
    }
}

Swift 6.2 handles this gracefully. The conformance is automatically isolated to the main actor, so the == function just works when called from main actor code. No awkward nonisolated workarounds needed.

Should You Enable It?

For new projects, you don’t have to do anything—Xcode 26 enables Approachable Concurrency by default.

For existing projects, I’d recommend migrating incrementally. Here’s why: enabling Approachable Concurrency changes how your code behaves, not just how it compiles. A function that used to run in the background might now run on the main actor.

The safest approach:

  1. Enable the individual upcoming features one at a time
  2. Look for places where the compiler suggests adding @concurrent
  3. Review any async code that does heavy work

In Xcode’s build settings, search for “nonisolated” or “approachable” to find the relevant flags.

For Swift Packages

If you maintain a Swift package, you’ll need to opt in explicitly. Update your Package.swift:

// swift-tools-version: 6.2

.target(
    name: "MyLibrary",
    swiftSettings: [
        .enableUpcomingFeature("NonisolatedNonsendingByDefault"),
        .enableUpcomingFeature("InferIsolatedConformances")
    ]
)

Note that packages created with SPM don’t get Approachable Concurrency by default, even in Xcode 26. This can create subtle differences between your app target and your packages—something to watch out for.

The Practical Impact

After using Swift 6.2 for a while, I can say the difference is noticeable. My projects have fewer spurious warnings. The compiler stopped yelling at me about sendability for types that never leave the main actor. And when I do need real concurrency, the @concurrent attribute makes my intent explicit.

Is it perfect? No. There’s still a learning curve, and async/await fundamentally changes how you think about code flow. But the barrier to entry is much lower now. You can adopt Swift Concurrency incrementally, starting with simple async calls and adding complexity only when you need it.

Wrapping Up

Approachable Concurrency is Swift’s acknowledgment that the concurrency model was asking too much, too soon. Most code doesn’t need parallelism—it just needs to wait for things. And now Swift lets you do that without drowning in annotations.

If you’ve been putting off the Swift 6 migration, this might be the push you needed. Enable Swift 6 mode, turn on Approachable Concurrency, and see how many warnings disappear. You might be surprised how manageable it’s become.