Swift 6 programming language with improved concurrency, type safety, and performance

Swift 6 is Apple’s biggest language release since Swift 5.5 shipped async/await. The headline is data-race safety enforced by the compiler: concurrency errors that used to surface at 2 a.m. on a user’s device now surface as build errors during swift build. Everything else — the new Swift Testing framework, any Sendable refinements, typed throws, region-based isolation, cross-platform Foundation — supports that single shift in how Swift treats concurrency.

This playbook is the version of “what’s new in Swift 6” we wish we had when we started migrating our own iOS apps. It is decision-focused: what each feature buys you in production, what it costs during migration, and when to turn it on in a real codebase rather than a toy sample. Every example comes from patterns we use shipping video-conferencing, streaming, and AI-powered iOS products for our clients.

Key takeaways

Swift 6 turns data races into compile errors. Full strict concurrency checking catches shared-mutable-state bugs at build time, not at crash time. The migration is real work, but the production win is bigger than any Swift upgrade since ARC.

Region-based isolation keeps ceremony low. You do not have to paint every type with Sendable. The compiler tracks which values flow across actor boundaries and only complains when a real race is possible.

Swift Testing replaces XCTest-first reflexes. Macro-powered @Test, #expect, parameterized tests, and tags deliver better ergonomics than XCTest — and it runs side by side with XCTest during migration.

Typed throws and noncopyable types close edge cases. Error typing makes library boundaries explicit; ~Copyable supports RAII-style resources (file handles, locks, tokens) without reference semantics.

Cross-platform Swift is finally credible. Swift-based Foundation unifies macOS, iOS, Linux, and Windows. Combined with Swift-on-Android (2025 Working Group), Swift is now a viable choice for shared business logic across every major platform.

Why Fora Soft wrote this Swift 6 playbook

Fora Soft ships iOS products in Swift for EdTech, e-health, media, and enterprise video clients. We migrated our own production codebases to Swift 6 through 2024–2025, including the iOS client for BrainCert’s learning platform, the iOS app behind SuperPower FX real-time video effects, and mobile review tooling for VALT enterprise video. Each migration hit a different flavour of concurrency trap, so the trade-offs below are tested, not speculative.

We use Agent Engineering (senior engineers supervising Cursor/Claude/Copilot) to keep Swift 6 migrations under budget: the compiler’s stricter diagnostics pair unusually well with AI-assisted rewrites, because every proposed fix has to survive a stricter type and concurrency check before it lands. Our Swift 6 migration estimates typically come in 20–40% below standard agency rates as a result.

Planning a Swift 6 migration for a production iOS app?

Send us your current Swift version, concurrency style, and app scale. We’ll return a concrete migration plan, risk list, and a tight estimate — usually well below a rewrite-everything quote.

Book a 30-min scoping call → WhatsApp → Email us →

A compressed timeline — from Swift 5.1 to Swift 6

Swift 6 is the pay-off of a five-year arc. Each 5.x release added a piece of the concurrency model; Swift 6 flipped the defaults and enforced it in the type system.

Version Released Headline feature Why it mattered
Swift 5.1 Sep 2019 Opaque return types, module stability Unblocked SwiftUI and binary distribution
Swift 5.5 Sep 2021 async/await, actors, Sendable Native concurrency replaces GCD callbacks
Swift 5.7 Sep 2022 Existential any, generic some Made generics vs. existentials explicit
Swift 5.9 Sep 2023 Macros, noncopyable types (preview) Compile-time code generation, RAII patterns
Swift 5.10 Mar 2024 Complete data isolation (opt-in) Final rehearsal for Swift 6 defaults
Swift 6.0 Sep 2024 Strict concurrency by default, Swift Testing Data-race safety enforced at compile time

Strict concurrency checking by default

The flagship Swift 6 change is that complete concurrency checking is on by default under language mode -swift-version 6. The compiler rejects any code path that could share mutable state across isolation domains. Where Swift 5.x might emit a warning, Swift 6 emits a build error.

In practice this means: classes that are not Sendable cannot be captured by @Sendable closures; values crossing an actor boundary must be transferable; and global mutable state without isolation is a compile error. The real migration cost is proving which pieces of your code are actually safe — and fixing the pieces that were not.

// Swift 5: compiles with a warning
class CounterUnsafe {
  var value = 0
}
let c = CounterUnsafe()
Task.detached { c.value += 1 }  // data race, warning only

// Swift 6: compile error unless `Counter` is an actor
// or Sendable-safe
actor Counter {
  private(set) var value = 0
  func bump() { value += 1 }
}
let counter = Counter()
Task { await counter.bump() }   // compiler is happy

Reach for strict concurrency when: you are already on Swift 5.10 with complete checking enabled, your CI is green, and the team is comfortable with actors and async/await. If those three are not true, migrate there first — then flip the language mode.

Region-based isolation (SE-0414) — fewer Sendable annotations

The feature that makes strict concurrency actually tolerable is region-based isolation. Instead of demanding every type be Sendable, the compiler tracks which values flow across isolation boundaries and only objects when a real race is possible.

In a typical app migration, region-based isolation cuts the number of Sendable annotations you need to add by 50–70%. A non-Sendable value created on MainActor and passed to a Task on the same actor is fine; the compiler only complains when it sees that value escape into a different isolation domain.

Practical effect: fewer diffs to review, fewer mechanical extension Foo: @unchecked Sendable {} escape hatches that rot in your codebase, and less ceremony blocking the path to data-race safety.

Sending parameters and values (SE-0430)

The sending keyword marks values that the compiler is allowed to transfer across an isolation boundary — typically because the caller no longer uses the value after the call. This replaces whole classes of “make it Sendable or clone it” dilemmas with a precise, local annotation.

actor Uploader {
  func send(_ payload: sending Payload) async throws {
    // The compiler verifies the caller released the Payload.
    // We can safely mutate it across the actor boundary.
  }
}

For video-processing and streaming code, where we routinely pass buffers across pipelines, sending is the feature that made strict concurrency practical on SuperPower FX. Buffers are transient; marking them sending is closer to the truth than making them Sendable.

Reach for sending when: the caller passes a non-Sendable value to an async boundary and does not retain or use it afterwards. Typical fits: buffers, intermediate results, single-use request payloads.

Typed throws (SE-0413) — explicit error contracts

Swift 6 introduces typed throws: functions can now declare the specific error type they can throw, not just throws as an open set.

enum UploadError: Error { case network, serialization, size }

func upload(_ file: URL) throws(UploadError) -> UploadID {
  // Only UploadError cases compile.
}

Use typed throws at library boundaries (SDK entry points, network layer, file I/O) where the set of possible failures is small and exhaustive. Inside implementation code, stick to untyped throws — locking the error type too deeply causes painful refactors when a new failure mode lands.

Reach for typed throws when: you are defining an SDK or framework boundary, the error set is stable and small, and callers benefit from exhaustive switch handling. Not on every internal function.

Stuck on Sendable warnings or actor deadlocks?

We have untangled Swift 6 concurrency on real production apps — including video pipelines, networking SDKs, and SwiftUI apps. Send us the file path or error message; we’ll scope a focused fix sprint.

Book a 30-min call → WhatsApp → Email us →

Noncopyable types (SE-0390 / SE-0437) — value-type RAII

Swift 6 promotes noncopyable types (~Copyable) from preview to stable, with broader standard-library adoption. Use them for values that represent a unique resource: a file descriptor, a mutex token, a GPU buffer handle, an in-flight network transaction. The compiler prevents accidental duplication, so you cannot close the same file twice or leak a lock.

struct FileHandle: ~Copyable {
  private let fd: CInt
  init(_ path: String) throws { /* open fd */ }
  consuming func close() { /* close fd */ }
  deinit { /* close if not consumed */ }
}

func write(to file: borrowing FileHandle) { /* ... */ }

This is the Swift answer to C++ move semantics and Rust ownership without the full Rust borrow checker tax. For mobile-video code dealing with GPU buffers, hardware encoders, and capture sessions, it prevents a whole class of subtle leaks.

Reach for ~Copyable when: the value represents a unique handle or ownership token — file descriptor, mutex, GPU buffer, hardware session — where accidental duplication is a bug, not a convenience.

Swift Testing — the XCTest replacement that ships with Xcode

Swift Testing is the new macro-based framework that ships alongside Swift 6. It replaces XCTest’s XCTAssert chain with a single #expect and adds first-class parameterized tests, tags, traits, and suites.

import Testing

@Suite("Uploader")
struct UploaderTests {
  @Test(arguments: [1024, 5 * 1024 * 1024, 100 * 1024 * 1024])
  func respectsSizeLimit(size: Int) async throws {
    let result = try await upload(size: size)
    #expect(result.status != .rejected)
  }
}

Swift Testing co-exists with XCTest, so the migration is feature-by-feature: write new tests in Swift Testing, leave existing XCTest suites alone until you are touching the file anyway. By the time you have migrated the high-churn surfaces, you will have replaced the majority of assertions without a big-bang rewrite.

We pair Swift Testing with AI QA tools covered in our QA process article: Swift Testing for unit and fast-integration, Reflect Mobile / Zentester for end-to-end UI flows. That combination is what keeps our release cadence predictable on complex video apps.

Swift-based Foundation — macOS, iOS, Linux, Windows, Android

Swift 6 completes the Swift-based Foundation re-implementation. The behaviour of Date, URL, JSONEncoder, String, and friends is consistent across Apple and non-Apple platforms, with performance close to or better than the Objective-C Foundation on Apple devices.

Combined with the 2025 Android Working Group, Swift is now a credible shared-logic language across iOS, Android, macOS, Linux, and Windows. For the first time since Objective-C, a single language can carry your domain model through every target in a full-stack product — see the Summer 2025 digest entry on Swift on Android for the wider context.

Macros grown up — practical patterns

Macros shipped in Swift 5.9 and matured in 6.0 with better diagnostics, stable tooling, and a growing set of first-party macros (@Observable, @Suite, @Test). Four places macros earn their keep in our apps:

1. Observation. @Observable replaces ObservableObject/@Published boilerplate with a single attribute, and it plays cleanly with strict concurrency.

2. Code generation for serialization. Custom @CodedAt / @Freestanding macros remove the tedious Codable key-path dance on domain types.

3. Testing ergonomics. @Test, #expect, and #require collapse three lines of XCTest into one.

4. Build-time constants. Bring API keys or remote-config schemas into the type system at compile time rather than stringly-typed at runtime.

A staged migration plan that survives real releases

Trying to flip a production app to Swift 6 in a single sprint is how weekends get ruined. We stage the migration across four phases, each of which ships behind the same release train you already run.

Phase 1 — Ground the codebase on Swift 5.10 with complete checking. Enable SWIFT_STRICT_CONCURRENCY=complete. Ship with warnings. Track a warnings burn-down chart in CI so regressions are visible.

Phase 2 — Replace global mutable state with actors or @MainActor-annotated singletons. Most warning volume comes from this bucket. Resist the urge to @unchecked Sendable your way out; the compiler is almost always right.

Phase 3 — Retrofit library boundaries. Add Sendable, sending, and typed throws to SDK-facing surfaces. Internal code can lag.

Phase 4 — Flip to -swift-version 6. With warnings at zero, the mode switch is a quiet day. Celebrate quietly; the work was in phases 2 and 3.

Cost math — what a Swift 6 migration looks like in engineer-weeks

Ballpark from our own production migrations. Numbers vary heavily with codebase hygiene; apps already on async/await land at the low end.

1. Small iOS app (<50k lines, mostly async/await). 2–4 engineer-weeks, mostly annotating Sendable and fixing a handful of global state pain points.

2. Mid-size app (50k–200k lines, mixed GCD and async). 6–10 engineer-weeks. The big bucket is rewiring GCD queues to actors and untangling shared-state singletons.

3. Large app (200k+ lines, heavy Objective-C bridging). 12–20 engineer-weeks, phased over two or three releases.

4. SDK with public API. Add 2–3 weeks on top for carefully versioned Sendable/sending/typed-throws annotations so downstream apps can migrate at their own pace.

Agent Engineering shortens each bucket by 20–40% in our experience, because the strict compiler pairs unusually well with AI-proposed fixes: every suggestion has to compile under the new mode, so false positives from the AI get filtered instantly. For a defendable estimate on your codebase, book a scoping call.

Mini case — Swift 6 on a real-time video app

Situation. A production iOS app with real-time video capture, on-device ML inference, and a GCD-era networking stack. Swift 5.10 complete checking emitted 400+ warnings. Goal: flip to Swift 6 without regressing the release cadence (every two weeks) or the frame-drop KPI.

12-week plan. Weeks 1–3: burn-down of global singleton warnings by introducing a @MainActor-isolated AppState actor and migrating three GCD queues into a capture pipeline actor. Weeks 4–7: annotate the video and networking SDK surfaces with sending and Sendable, land Swift Testing suites for the two highest-churn modules. Weeks 8–10: close out the last 50 warnings mostly with small refactors, plus a single @unchecked Sendable on an intentionally shared cache with a documented justification. Weeks 11–12: flip -swift-version 6, ship behind a flag, rollout.

Outcome. Concurrency-related crashes fell sharply in the weeks following the flip, because several latent data races surfaced during the migration and were fixed before release. Frame-drop KPI and release cadence stayed unchanged.

A decision framework — should you adopt Swift 6 this quarter?

1. Is your codebase already on async/await? If no, migrate to async/await first; Swift 6’s benefits are multiplicative on an async-native codebase.

2. Do you have complete checking enabled in 5.10? If yes, the hard work is already behind you; flip the mode in the next release. If no, that is phase 1.

3. Is your team comfortable with actors and isolation? Plan a focused learning sprint before the migration — two days of real examples beat two weeks of grumbling at compile errors.

4. Are you shipping a public SDK? Add an extra buffer for versioned annotation work and a clear migration guide for your downstream consumers.

5. What is your crash-bucket concurrency rate? If concurrency-related crashes are a notable share of your top-5 crashes, the migration pays for itself in the first post-migration release. If they are not, plan around feature work and ship the migration opportunistically.

Pitfalls to avoid

1. Reaching for @unchecked Sendable too quickly. Every escape hatch is technical debt. If you truly need it, document why; if you do not, fix the design instead.

2. Over-annotating internal code with typed throws. Save typed throws for library boundaries. Internal code under evolution is safer with untyped throws.

3. Migrating and shipping feature work in the same sprint. Warnings burn-down sprints stay clean only when they are the only thing happening in that file.

4. Ignoring Swift Testing until the end. Adding the first @Test early teaches the team a lot about the new macros before you need them under deadline.

5. Underestimating Objective-C bridging. Obj-C-bridged APIs are the noisiest source of Sendable gaps. Budget extra time for any app with a significant Obj-C core.

KPIs — what to measure after adopting Swift 6

Quality KPIs. Concurrency-class crashes per 10k sessions (target: approaching zero), compile-time warnings in CI (target: zero), and unit-test suite flake rate (expect improvement as Swift Testing replaces flakier XCTest async patterns).

Business KPIs. Time-to-fix for concurrency-triggered hotfixes (before vs. after), developer ramp-up time on the codebase (new hires should onboard faster once the compiler teaches them the concurrency model), and release cadence stability.

Reliability KPIs. Main-thread stalls (@MainActor scopes you now own explicitly), ANR-equivalent hangs, and cold-start time (usually improves, sometimes mildly regresses, always worth tracking).

When not to migrate yet

Not every app should flip to Swift 6 this quarter. Hold off if your team is mid-migration to SwiftUI or async/await (do one migration at a time), if your app still relies on substantial Objective-C code that you are not ready to bridge, or if your release window is already compressed by platform events (App Store submission freeze, major OS launch window).

Swift 6 will still be there next quarter. Shipping a broken release because the migration collided with a feature push will not.

Need a Swift 6 migration plan you can trust?

Our iOS team has migrated production apps with real-time video, ML, and Obj-C bridges to Swift 6. Send us the repo link or a short description — we’ll come back with a staged plan and a tight estimate.

Book a 30-min call → WhatsApp → Email us →

FAQ

Can I adopt Swift 6 features without flipping the language mode?

Yes. Most Swift 6 features (region-based isolation, sending, typed throws, noncopyable types, Swift Testing) land in Swift 5.10 or later with opt-in flags. Many teams enable them incrementally for a release or two before flipping -swift-version 6.

Does Swift 6 break my existing codebase?

Only if you enable language mode 6. Swift 6 compilers can still build Swift 5 code — the stricter behaviour is opt-in per module. Use that to migrate module-by-module.

Should we switch from XCTest to Swift Testing immediately?

No rush. Swift Testing co-exists with XCTest. Write new tests in Swift Testing and migrate existing suites opportunistically when you are already editing the file. A forced, big-bang rewrite buys you nothing that an incremental approach does not.

How does Swift 6 interact with SwiftUI apps?

SwiftUI is annotated @MainActor, which plays cleanly with strict concurrency. Plan for extra work if you interact with non-isolated view-model singletons or pass non-Sendable closures into SwiftUI actions — the compiler now calls out patterns that used to slide.

What is the typed-throws rule of thumb?

Use typed throws at SDK/framework boundaries where the error set is stable and callers benefit from exhaustive handling. Keep internal code on untyped throws so evolving error modes do not force cascading refactors.

Can we use Swift 6 for Linux or Windows back-end work?

Yes. Swift 6 with Swift-based Foundation is a credible back-end language on Linux and Windows. Pair with Vapor or Hummingbird for HTTP APIs. Teams already on Swift on the client side benefit from a single language across stack boundaries.

How does Swift 6 affect Swift on Android plans?

The 2025 Swift-on-Android Working Group builds on the Swift 6 toolchain. Shared business logic in Swift 6 with strict concurrency is the cleanest starting point for new cross-platform modules. SwiftUI remains iOS-only; native UI per platform is still the recommendation.

How much does a Swift 6 migration typically cost?

A small app migration is typically 2–4 engineer-weeks; a mid-size app 6–10; a large app 12–20, phased over releases. With Agent Engineering we regularly come in 20–40% below those ranges. Every quote is shaped by the specific codebase, so numbers above are starting points, not contracts.

Deep dive

Swift 6 iOS Development

Build next-gen video chat apps with Swift 6 — the production companion to this feature overview.

SwiftUI

SwiftUI Video Conferencing vs UIKit

Where SwiftUI wins, where UIKit still earns its keep, and how Swift 6 changes the trade-off.

SPM

Swift Package Manager for Video Apps

Module boundaries and Sendable annotations across packages — the practical companion to Swift 6 migration.

Context

Summer 2025 Tech Digest

iOS 26, Swift on Android, GPT-5 — the broader release landscape around Swift 6.

QA

Our Testing Process

How Swift Testing fits into the broader Fora Soft QA playbook.

Ready to make Swift 6 safe for your production app?

Swift 6 is the real deal: data-race safety at compile time, better testing ergonomics, noncopyable resources, typed errors at library boundaries, and a cross-platform Foundation that finally makes one language a credible choice across iOS, Android, macOS, Linux, and Windows. The migration is real work, but every production app we have moved has come out measurably safer and easier to reason about.

Stage the migration across releases, lean on region-based isolation and sending to keep the Sendable ceremony low, and introduce Swift Testing where you are already writing tests. When you want engineers who have done this on shipping apps, we’re a 30-minute call away.

Ship Swift 6 with confidence

Tell us your Swift version, app size, and timeline. We’ll come back with a staged migration plan, a defendable estimate, and the two concurrency bets you should make first.

Book a 30-min call → WhatsApp → Email us →

  • Development