🤍
Good news! Apple has released a new version Xcode 16.
Swift 6 compiler, is the 1st version of Swift 6 language mode means, when you install Xcode 16, you get the new llvm compiler and you get Swift 6 language support with it. We can check your version by running swiftc –version
in terminal. Because Swift 6 language mode – is an opting feature (not a mandatory one).
Xcode 16 and Swift 6
🤍
Either in your build settings or package manifest
You can begin leveraging new features from Swift 6, such as strict concurrency checking, without fully upgrading your project to Swift 6. This process, known as Incremental adoption,
It allows you to gradually implement these updates rather than adopting all the changes at once.
To enable new language features, navigate to your project's build settings, search for Swift Compiler - Upcoming Features, and set the features you want to adopt to Yes.
Lets you integrate Swift 6 features progressively, ensuring a smoother transition.
For Swift Package Manager (SPM) packages, you can achieve the same incremental adoption using the .enableUpcomingFeature
API.
Allows you to specify and enable individual features that you want to integrate into your package.
By using this approach, you can selectively update your package with new Swift 6 features while maintaining compatibility with earlier versions.
Enabling Swift 6 upcoming features
🤍
Either in your build settings or package manifest
To upgrade your project to Swift 6 language mode, navigate to your build settings and select Swift Compiler - Language > Swift Language Version. This upgrade enables all of the new features introduced in Swift 6, including strict concurrency checking.
Note: After upgrading, you may notice that a few features planned for future Swift versions are still listed but disabled by default in Swift 6. For example, the ExistentialAny (SE-335), which was initially planned for Swift 6, was postponed by the Language Steering Group and is not enabled by default in this version.
For Swift Package Manager (SPM) packages, don’t confuse the swift-tools-version with the Swift language version. The swift-tools-version
specified at the top of your Package.swift
Package.swift file only sets the minimum Swift version required to build the package. To upgrade your package to Swift 6, you need to explicitly adopt the following Swift setting:
.swiftLanguageVersion(.v6)
Setting Swift language version in SPM
Update Swift Language Version Mode
🤍
Either in your build settings or package manifest
If you only want to enable strict concurrency checking, you can update your build settings accordingly. Some of you might have already experimented with this, as it was available in Xcode 15 for projects using Swift 5.10. Has anyone tried it in Xcode 15 yet?
There are three levels of concurrency checking to choose from:
Minimal
: This enforces Sendable constraints only where they’ve been explicitly adopted and performs actor-isolation checks for code that has adopted concurrency.
Targeted
: This level performs actor-isolation checks and applies Sendable constraints to explicitly adopted code.
Completed
: Enforces Sendable constraints and actor-isolation checks across the entire project or module.
Each step introduces stricter checking, potentially leading to more warnings. It’s important to proceed gradually, adopting each level one at a time. After resolving the warnings for each level, you can open a pull request and move on to the next level.
Enable Strict Concurrency Check
Swift 6 brings significant advancements beyond Swift concurrency, with proposals focusing on improved type safety, performance optimizations, and language consistency.
Key changes include Strict Concurrency Checking, the introduction of ExistentialAny for clearer type usage, enhanced Sendable
and actor
isolation models, and refined macro support for code generation.
Swift 6 also delivers optimizations for generics, improved memory management, and a more expressive pattern matching system, making it a robust upgrade for modern Swift development. These changes set the stage for both safer code and better performance across projects.
Swift 6 introduces several concurrency-focused proposals that significantly enhance the language's safety and performance.
SE Proposals - Swift 6
Swift 6 introduces several breaking changes, which are opt-in. Along with new features, many previously accepted proposals, marked as "upcoming features," will now be enabled by default.
In Xcode, under Build Settings > Upcoming Features, you can find the breaking changes listed on the left, with corresponding options on the right to help manage these updates in your project.
Swift 6 introduces a new language mode that eliminates data races at compile time by enforcing data isolation.
What is Data Isolation?
Mechanism - used to make data races impossible
Deals with accessing mutable state in unsafe ways not all data races
Isolation can change when you use await keyword
Isolation applies to variables, functions, closures, protocols
Why is Data Isolation Important?
To identify data races at compile time
How Does Data Isolation Work?
Compiler validates data passed over boundaries is either safe or mutually exclusive
Where Does Data Isolation Apply?
variables, functions, closures, protocols
Data Isolation is Similar to…?
Conceptually similar to a lock
Apart from flavours, we have important concepts in Isolation.
Data Isolation
Methods and variables are isolated into distinct regions, where each is classified as non-isolated, actor-isolated, or global actor-isolated
Example of Isolation Domain
The boundary between isolation domains
Example of Isolation Boundary
Moving values into or out of an isolation domain is known as crossing an isolation boundary
Crossing Isolation Boundary
The addWithGlobalIsolation
method, which is actorIsolated, attempts to access the globallyIsolatedValue
, which is isolated by a global actor, asynchronously. This demonstrates crossing between two isolation domains. Additionally, values can also cross boundaries indirectly when captured by closures.
Remember, incremental migration is recommended when transitioning to Swift 6.
If you're using Xcode 15 and Swift 5.9, you'll need to enable the experimentalFeature
compiler flag to activate strict concurrency checking.
In Xcode 16 with Swift 5, you'll use the upcomingFeature
flag for this purpose.
In Xcode 16 with Swift 6, strict concurrency checks are enabled by default.
Incremental Migration
Let’s look at some important breaking changes and tips for preparing for strict concurrency checking:
Global state, such as static variables, has traditionally been accessible from anywhere in a program, making them vulnerable to concurrent access. Before Swift's data-race safety, it was up to the programmer to handle these carefully, without compiler support.
Now, in Swift 6, Global or Static Variables are not concurrency-safe in non-isolated contexts, and the compiler won't allow them either. To address this:
Convert global or static variables to let
constants and ensure the type conforms to Sendable
.
If the value is used only within a specific concurrency domain, you can add a global actor attribute (e.g., @MainActor
) to its declaration.
If the type's usage is concurrency-safe, you can mark it as nonisolated(unsafe) after auditing the type.
Strict concurrency for global variables
Swift ensures data race safety through two core principles: actor isolation and region isolation. These mechanisms control where values are stored, where execution occurs, and how sendability is enforced.
In earlier versions of Swift, sending a non-Sendable type, such as a reference type, across isolation boundaries led to potential data race issues. For example, before Swift 6, attempting to transfer an object from @MainActor in OpenNewAccount to SharedActor in ClientStore.shared would trigger a compiler error. This happened because concurrent access to the object was unsafe.
Swift 6 introduces region analysis with flow-sensitive detection, enabling smarter handling of non-Sendable types. Even if an object is not explicitly Sendable, Swift 6 can now analyze whether the value is no longer accessed in its original isolation context after being transferred. This ensures that ownership shifts safely to the new actor without causing race conditions.
These improvements make it easier to work with non-Sendable types when passing data across actors, enhancing Swift’s concurrency model for better efficiency and safety.
Region Based Isolation
Earlier, isolated parameters required an actor instance, making non-isolated calls impossible. Swift 6 resolves this by allowing optional isolated parameters, letting functions accept nil and behave as non-isolated when needed.
Additionally, determining the current isolation context previously required manual workarounds. Swift 6 introduces #isolation, enabling direct access to the isolation state, reducing boilerplate and improving concurrency management.
These updates make Swift 6’s concurrency model more flexible and efficient.
Inheritance of actor Isolation
Swift 6 introduces dynamic actor isolation enforcement, improving concurrency in non-strict contexts.
When conforming to a protocol from an external module, the compiler may reject MainActor instances for non-isolated requirements. A quick fix is using nonisolated, but this compromises static data race safety.
A better solution is @preconcurrency, which preserves runtime behavior while ensuring static isolation and thread safety. This makes working across actor boundaries safer and more flexible.
Dynamic actor isolation enforcement
In Swift 6, non-Sendable values can be safely transferred between actors using the Sending keyword.
Previously, passing a non-Sendable value from a non-isolated async function to an actor like @MainActor resulted in a compiler error due to potential data races. Swift couldn’t verify safe usage across all call sites.
Swift 6 solves this by introducing Sending, which transfers ownership of the value, ensuring safe passage between concurrency domains. Marking a parameter or return type with Sending disconnects it from the original context, preventing unintended access.
Sending parameter and result values
Swift ensures static isolation for most declarations, but closures behave differently. Their isolation depends not only on where they are defined but also on the values they capture. This can lead to a loss of static isolation context.
With Swift 6, adding @isolated(any) to a closure ensures it must always be called with await, even when it doesn’t explicitly cross an isolation boundary. This allows the closure to retain isolation details from its defining context. While closures typically don’t have properties, @isolated(any) introduces a specialized type that preserves such information.
Key Use Cases:
Non-isolated functions – By default, functions in Swift are non-isolated unless explicitly specified.
Functions tied to a global actor – These execute within a specific actor, such as MainActor.
Functions tied to an actor instance – These operate within the isolation of the actor instance passed to them.
By enforcing asynchronous execution, @isolated(any) helps closures maintain actor isolation, improving concurrency safety in Swift 6.
@isolated(any) function types
In Xcode 16, Apple made a significant adjustment to the View protocol in SwiftUI. Previously, only the body
property was annotated with @MainActor
, which could lead to confusion. Now, the entire View protocol is annotated with @MainActor
, not just body
. This change is backwards compatible, meaning that all types conforming to the View protocol will automatically inherit the @MainActor
annotation.
You might be wondering what this change means in practice. For example, if you run a check in Xcode 15, it will return false because only the body
is marked as @MainActor
. But in Xcode 16, this will return true since the entire view is now @MainActor
.
If you want to exit this isolation context, you'll need to use a non-isolated context, which will still return false in both Xcode versions.
SwiftUI Implications
Handling actor reentrancy, task cancellation, and managing related warnings and errors can be tricky in Swift. The migration from existing code to the new concurrency model may also feel unclear, as it involves refactoring for actor isolation and async tasks.
Challenges include:
Actor reentrancy: Preventing unexpected behavior when actors handle multiple tasks.
Cancellation: Managing task interruptions smoothly.
Migration: Refactoring older code to fit the new concurrency rules, which can be confusing.
Taking a gradual approach to migration can make the process easier.
Challenges of Isolation
You can track the adoption of Swift 6 in popular packages by visiting swiftpackageindex.com. This platform provides updates on package compatibility and how widely Swift 6 features are being integrated across the ecosystem.
When to Upgrade to strict checking
Migrating to Swift 6 varies by project size. For a smoother process, adopt Swift 6 incrementally.
Start with an isolated part of your project—like a target or module. Enable Swift 6 features step by step, fixing warnings as you go. Gradually increase concurrency checks and, once issues are resolved, switch fully to Swift 6.
Focus on small, isolated parts like app extensions or individual modules. If your app has multiple modules, migrate one at a time, starting with the app itself and then its dependencies. This makes the process more manageable.
Ensure smoother migration
Checkout the YouTube link here - https://www.youtube.com/watch?v=dkhaZcL4wnY&ab_channel=SwiftPackageIndex
Swiftpackageindex Podcast
SPONSOR
The World's Northernmost iOS Conference
This is the first ever World's Northernmost Apple Developers' Conference with One day of workshops, two days of tech-talks, Sauna 🧖, ice swimming 🏊, northern lights 🗻, and drinks to help you forget about work and connect with like-minded professionals, it is organised by Jesse Sipola
Get your tickets!Warning-Free Compilation is Not the Ultimate Goal: The focus is on improving code safety and performance, not just eliminating warnings.
Clear Areas for Improvement: The migration process highlights key areas needing attention, making it easier to address potential issues.
Improved Safety: Swift 6 enhances safety, particularly with strict concurrency and isolation checks, making code more reliable.
Takeaways
Please check out basics of Swift Concurrency (introduced in Swift 5.5) - Strict Concurrency in Swift 6 - Part 1
This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.