mirror of
https://github.com/steipete/agent-rules.git
synced 2025-12-16 12:00:09 +01:00
Add Swift 6 concurrency migration guide
Comprehensive guide covering: - Data race safety and isolation domains - Common compiler errors and solutions - Migration strategy and incremental adoption - Build configuration for Swift 6 language mode - Runtime behavior and source compatibility
This commit is contained in:
794
swift6-migration-compact.md
Normal file
794
swift6-migration-compact.md
Normal file
@@ -0,0 +1,794 @@
|
||||
# The Swift Concurrency Migration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Swift's concurrency system, introduced in Swift 5.5, makes asynchronous and parallel code easier to write and understand.
|
||||
With the Swift 6 language mode, the compiler can now guarantee that concurrent programs are free of data races.
|
||||
|
||||
Adopting the Swift 6 language mode is entirely under your control on a per-target basis.
|
||||
Targets that build with previous modes can interoperate with modules that have been migrated to the Swift 6 language mode.
|
||||
|
||||
> Important: The Swift 6 language mode is _opt-in_.
|
||||
Existing projects will not switch to this mode without configuration changes.
|
||||
There is a distinction between the _compiler version_ and _language mode_.
|
||||
The Swift 6 compiler supports four distinct language modes: "6", "5", "4.2", and "4".
|
||||
|
||||
# Data Race Safety
|
||||
|
||||
Learn about the fundamental concepts Swift uses to enable data-race-free concurrent code.
|
||||
|
||||
Traditionally, mutable state had to be manually protected via careful runtime synchronization.
|
||||
Using tools such as locks and queues, the prevention of data races was entirely up to the programmer.
|
||||
This is notoriously difficult not just to do correctly, but also to keep correct over time.
|
||||
|
||||
More formally, a data race occurs when one thread accesses memory while the same memory is being mutated by another thread.
|
||||
The Swift 6 language mode eliminates these problems by preventing data races at compile time.
|
||||
|
||||
## Data Isolation
|
||||
|
||||
Swift's concurrency system allows the compiler to understand and verify the safety of all mutable state.
|
||||
It does this with a mechanism called _data isolation_.
|
||||
Data isolation guarantees mutually exclusive access to mutable state.
|
||||
|
||||
### Isolation Domains
|
||||
|
||||
Data isolation is the _mechanism_ used to protect shared mutable state.
|
||||
An _isolation domain_ is an independent unit of isolation.
|
||||
|
||||
All function and variable declarations have a well-defined static isolation domain:
|
||||
|
||||
1. Non-isolated
|
||||
2. Isolated to an actor value
|
||||
3. Isolated to a global actor
|
||||
|
||||
### Non-isolated
|
||||
|
||||
Functions and variables do not have to be a part of an explicit isolation domain.
|
||||
In fact, a lack of isolation is the default, called _non-isolated_.
|
||||
|
||||
```swift
|
||||
func sailTheSea() {
|
||||
}
|
||||
```
|
||||
|
||||
This top-level function has no static isolation, making it non-isolated.
|
||||
|
||||
```swift
|
||||
class Chicken {
|
||||
let name: String
|
||||
var currentHunger: HungerLevel
|
||||
}
|
||||
```
|
||||
|
||||
This is an example of a non-isolated type.
|
||||
|
||||
### Actors
|
||||
|
||||
Actors give the programmer a way to define an isolation domain, along with methods that operate within that domain.
|
||||
All stored properties of an actor are isolated to the enclosing actor instance.
|
||||
|
||||
```swift
|
||||
actor Island {
|
||||
var flock: [Chicken]
|
||||
var food: [Pineapple]
|
||||
|
||||
func addToFlock() {
|
||||
flock.append(Chicken())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, every `Island` instance will define a new domain, which will be used to protect access to its properties.
|
||||
The method `Island.addToFlock` is said to be isolated to `self`.
|
||||
|
||||
Actor isolation can be selectively disabled:
|
||||
|
||||
```swift
|
||||
actor Island {
|
||||
var flock: [Chicken]
|
||||
var food: [Pineapple]
|
||||
|
||||
nonisolated func canGrow() -> PlantSpecies {
|
||||
// neither flock nor food are accessible here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Global Actors
|
||||
|
||||
Global actors share all of the properties of regular actors, but also provide a means of statically assigning declarations to their isolation domain.
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
class ChickenValley {
|
||||
var flock: [Chicken]
|
||||
var food: [Pineapple]
|
||||
}
|
||||
```
|
||||
|
||||
This class is statically-isolated to `MainActor`.
|
||||
|
||||
### Tasks
|
||||
|
||||
A `task` is a unit of work that can run concurrently within your program.
|
||||
Tasks may run concurrently with one another, but each individual task only executes one function at a time.
|
||||
|
||||
```swift
|
||||
Task {
|
||||
flock.map(Chicken.produce)
|
||||
}
|
||||
```
|
||||
|
||||
A task always has an isolation domain. They can be isolated to an actor instance, a global actor, or could be non-isolated.
|
||||
|
||||
### Isolation Inference and Inheritance
|
||||
|
||||
There are many ways to specify isolation explicitly.
|
||||
But there are cases where the context of a declaration establishes isolation implicitly, via _isolation inference_.
|
||||
|
||||
#### Classes
|
||||
|
||||
A subclass will always have the same isolation as its parent.
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
class Animal {
|
||||
}
|
||||
|
||||
class Chicken: Animal {
|
||||
}
|
||||
```
|
||||
|
||||
Because `Chicken` inherits from `Animal`, the static isolation of the `Animal` type also implicitly applies.
|
||||
|
||||
The static isolation of a type will also be inferred for its properties and methods by default.
|
||||
|
||||
#### Protocols
|
||||
|
||||
A protocol conformance can implicitly affect isolation.
|
||||
However, the protocol's effect on isolation depends on how the conformance is applied.
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
protocol Feedable {
|
||||
func eat(food: Pineapple)
|
||||
}
|
||||
|
||||
// inferred isolation applies to the entire type
|
||||
class Chicken: Feedable {
|
||||
}
|
||||
|
||||
// inferred isolation only applies within the extension
|
||||
extension Pirate: Feedable {
|
||||
}
|
||||
```
|
||||
|
||||
## Isolation Boundaries
|
||||
|
||||
Moving values into or out of an isolation domain is known as _crossing_ an isolation boundary.
|
||||
Values are only ever permitted to cross an isolation boundary where there is no potential for concurrent access to shared mutable state.
|
||||
|
||||
### Sendable Types
|
||||
|
||||
In some cases, all values of a particular type are safe to pass across isolation boundaries because thread-safety is a property of the type itself.
|
||||
This is represented by the `Sendable` protocol.
|
||||
|
||||
Swift encourages using value types because they are naturally safe.
|
||||
Value types in Swift are implicitly `Sendable` when all their stored properties are also Sendable.
|
||||
However, this implicit conformance is not visible outside of their defining module.
|
||||
|
||||
```swift
|
||||
enum Ripeness {
|
||||
case hard
|
||||
case perfect
|
||||
case mushy(daysPast: Int)
|
||||
}
|
||||
|
||||
struct Pineapple {
|
||||
var weight: Double
|
||||
var ripeness: Ripeness
|
||||
}
|
||||
```
|
||||
|
||||
Here, both types are implicitly `Sendable` since they are composed entirely of `Sendable` value types.
|
||||
|
||||
### Actor-Isolated Types
|
||||
|
||||
Actors are not value types, but because they protect all of their state in their own isolation domain, they are inherently safe to pass across boundaries.
|
||||
This makes all actor types implicitly `Sendable`.
|
||||
|
||||
Global-actor-isolated types are also implicitly `Sendable` for similar reasons.
|
||||
|
||||
### Reference Types
|
||||
|
||||
Unlike value types, reference types cannot be implicitly `Sendable`.
|
||||
To make a class `Sendable` it must contain no mutable state and all immutable properties must also be `Sendable`.
|
||||
Further, the compiler can only validate the implementation of final classes.
|
||||
|
||||
```swift
|
||||
final class Chicken: Sendable {
|
||||
let name: String
|
||||
}
|
||||
```
|
||||
|
||||
### Suspension Points
|
||||
|
||||
A task can switch between isolation domains when a function in one domain calls a function in another.
|
||||
A call that crosses an isolation boundary must be made asynchronously.
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
func stockUp() {
|
||||
// beginning execution on MainActor
|
||||
let food = Pineapple()
|
||||
|
||||
// switching to the island actor's domain
|
||||
await island.store(food)
|
||||
}
|
||||
```
|
||||
|
||||
Potential suspension points are marked in source code with the `await` keyword.
|
||||
|
||||
### Atomicity
|
||||
|
||||
While actors do guarantee safety from data races, they do not ensure atomicity across suspension points.
|
||||
Because the current isolation domain is freed up to perform other work, actor-isolated state may change after an asynchronous call.
|
||||
|
||||
```swift
|
||||
func deposit(pineapples: [Pineapple], onto island: Island) async {
|
||||
var food = await island.food
|
||||
food += pineapples
|
||||
await island.store(food)
|
||||
}
|
||||
```
|
||||
|
||||
This code assumes, incorrectly, that the `island` actor's `food` value will not change between asynchronous calls.
|
||||
Critical sections should always be structured to run synchronously.
|
||||
|
||||
# Common Compiler Errors
|
||||
|
||||
Identify, understand, and address common problems you can encounter while working with Swift concurrency.
|
||||
|
||||
After enabling complete checking, many projects can contain a large number of warnings and errors.
|
||||
_Don't_ get overwhelmed!
|
||||
Most of these can be tracked down to a much smaller set of root causes.
|
||||
|
||||
## Unsafe Global and Static Variables
|
||||
|
||||
Global state, including static variables, are accessible from anywhere in a program.
|
||||
This visibility makes them particularly susceptible to concurrent access.
|
||||
|
||||
### Sendable Types
|
||||
|
||||
```swift
|
||||
var supportedStyleCount = 42
|
||||
```
|
||||
|
||||
Here, we have defined a global variable that is both non-isolated _and_ mutable from any isolation domain.
|
||||
|
||||
Two functions with different isolation domains accessing this variable risks a data race:
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
func printSupportedStyles() {
|
||||
print("Supported styles: ", supportedStyleCount)
|
||||
}
|
||||
|
||||
func addNewStyle() {
|
||||
let style = Style()
|
||||
supportedStyleCount += 1
|
||||
storeStyle(style)
|
||||
}
|
||||
```
|
||||
|
||||
One way to address the problem is by changing the variable's isolation:
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
var supportedStyleCount = 42
|
||||
```
|
||||
|
||||
If the variable is meant to be constant:
|
||||
|
||||
```swift
|
||||
let supportedStyleCount = 42
|
||||
```
|
||||
|
||||
If there is synchronization in place that protects this variable:
|
||||
|
||||
```swift
|
||||
/// This value is only ever accessed while holding `styleLock`.
|
||||
nonisolated(unsafe) var supportedStyleCount = 42
|
||||
```
|
||||
|
||||
Only use `nonisolated(unsafe)` when you are carefully guarding all access to the variable with an external synchronization mechanism.
|
||||
|
||||
### Non-Sendable Types
|
||||
|
||||
Global _reference_ types present an additional challenge, because they are typically not `Sendable`.
|
||||
|
||||
```swift
|
||||
class WindowStyler {
|
||||
var background: ColorComponents
|
||||
|
||||
static let defaultStyler = WindowStyler()
|
||||
}
|
||||
```
|
||||
|
||||
The issue is `WindowStyler` is a non-`Sendable` type, making its internal state unsafe to share across isolation domains.
|
||||
|
||||
One option is to isolate the variable to a single domain using a global actor.
|
||||
Alternatively, it might make sense to add a conformance to `Sendable` directly.
|
||||
|
||||
## Protocol Conformance Isolation Mismatch
|
||||
|
||||
A protocol defines requirements that a conforming type must satisfy, including static isolation.
|
||||
This can result in isolation mismatches between a protocol's declaration and conforming types.
|
||||
|
||||
### Under-Specified Protocol
|
||||
|
||||
```swift
|
||||
protocol Styler {
|
||||
func applyStyle()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class WindowStyler: Styler {
|
||||
func applyStyle() {
|
||||
// access main-actor-isolated state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It is possible that the protocol actually _should_ be isolated, but has not yet been updated for concurrency.
|
||||
|
||||
#### Adding Isolation
|
||||
|
||||
If protocol requirements are always called from the main actor, adding `@MainActor` is the best solution:
|
||||
|
||||
```swift
|
||||
// entire protocol
|
||||
@MainActor
|
||||
protocol Styler {
|
||||
func applyStyle()
|
||||
}
|
||||
|
||||
// per-requirement
|
||||
protocol Styler {
|
||||
@MainActor
|
||||
func applyStyle()
|
||||
}
|
||||
```
|
||||
|
||||
#### Asynchronous Requirements
|
||||
|
||||
For methods that implement synchronous protocol requirements the isolation of implementations must match exactly.
|
||||
Making a requirement _asynchronous_ offers more flexibility:
|
||||
|
||||
```swift
|
||||
protocol Styler {
|
||||
func applyStyle() async
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class WindowStyler: Styler {
|
||||
// matches, even though it is synchronous and actor-isolated
|
||||
func applyStyle() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Preconcurrency Conformance
|
||||
|
||||
Annotating a protocol conformance with `@preconcurrency` makes it possible to suppress errors about any isolation mismatches:
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
class WindowStyler: @preconcurrency Styler {
|
||||
func applyStyle() {
|
||||
// implementation body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Isolated Conforming Type
|
||||
|
||||
Sometimes the protocol's static isolation is appropriate, and the issue is only caused by the conforming type.
|
||||
|
||||
#### Non-Isolated
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
class WindowStyler: Styler {
|
||||
nonisolated func applyStyle() {
|
||||
// perhaps this implementation doesn't involve
|
||||
// other MainActor-isolated state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Crossing Isolation Boundaries
|
||||
|
||||
The compiler will only permit a value to move from one isolation domain to another when it can prove it will not introduce data races.
|
||||
|
||||
### Implicitly-Sendable Types
|
||||
|
||||
Many value types consist entirely of `Sendable` properties.
|
||||
The compiler will treat types like this as implicitly `Sendable`, but _only_ when they are non-public.
|
||||
|
||||
```swift
|
||||
public struct ColorComponents {
|
||||
public let red: Float
|
||||
public let green: Float
|
||||
public let blue: Float
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func applyBackground(_ color: ColorComponents) {
|
||||
}
|
||||
|
||||
func updateStyle(backgroundColor: ColorComponents) async {
|
||||
await applyBackground(backgroundColor)
|
||||
}
|
||||
```
|
||||
|
||||
Because `ColorComponents` is marked `public`, it will not implicitly conform to `Sendable`.
|
||||
|
||||
A straightforward solution is to make the type's `Sendable` conformance explicit:
|
||||
|
||||
```swift
|
||||
public struct ColorComponents: Sendable {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Preconcurrency Import
|
||||
|
||||
Even if the type in another module is actually `Sendable`, it is not always possible to modify its definition.
|
||||
Use a `@preconcurrency import` to downgrade diagnostics:
|
||||
|
||||
```swift
|
||||
// ColorComponents defined here
|
||||
@preconcurrency import UnmigratedModule
|
||||
|
||||
func updateStyle(backgroundColor: ColorComponents) async {
|
||||
// crossing an isolation domain here
|
||||
await applyBackground(backgroundColor)
|
||||
}
|
||||
```
|
||||
|
||||
### Latent Isolation
|
||||
|
||||
Sometimes the _apparent_ need for a `Sendable` type can actually be the symptom of a more fundamental isolation problem.
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
func applyBackground(_ color: ColorComponents) {
|
||||
}
|
||||
|
||||
func updateStyle(backgroundColor: ColorComponents) async {
|
||||
await applyBackground(backgroundColor)
|
||||
}
|
||||
```
|
||||
|
||||
Since `updateStyle(backgroundColor:)` is working directly with `MainActor`-isolated functions and non-`Sendable` types, just applying `MainActor` isolation may be more appropriate:
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
func updateStyle(backgroundColor: ColorComponents) async {
|
||||
applyBackground(backgroundColor)
|
||||
}
|
||||
```
|
||||
|
||||
### Sending Argument
|
||||
|
||||
The compiler will permit non-`Sendable` values to cross an isolation boundary if the compiler can prove it can be done safely:
|
||||
|
||||
```swift
|
||||
func updateStyle(backgroundColor: sending ColorComponents) async {
|
||||
// this boundary crossing can now be proven safe in all cases
|
||||
await applyBackground(backgroundColor)
|
||||
}
|
||||
```
|
||||
|
||||
### Sendable Conformance
|
||||
|
||||
When encountering problems related to crossing isolation domains, you can make a type `Sendable` in four ways:
|
||||
|
||||
#### Global Isolation
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
public struct ColorComponents {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Actors
|
||||
|
||||
```swift
|
||||
actor Style {
|
||||
private var background: ColorComponents
|
||||
}
|
||||
```
|
||||
|
||||
#### Manual Synchronization
|
||||
|
||||
```swift
|
||||
class Style: @unchecked Sendable {
|
||||
private var background: ColorComponents
|
||||
private let queue: DispatchQueue
|
||||
}
|
||||
```
|
||||
|
||||
#### Sendable Reference Types
|
||||
|
||||
To allow a checked `Sendable` conformance, a class:
|
||||
|
||||
- Must be `final`
|
||||
- Cannot inherit from another class other than `NSObject`
|
||||
- Cannot have any non-isolated mutable properties
|
||||
|
||||
```swift
|
||||
final class Style: Sendable {
|
||||
private let background: ColorComponents
|
||||
}
|
||||
```
|
||||
|
||||
### Non-Isolated Initialization
|
||||
|
||||
Actor-isolated types can present a problem when they are initialized in a non-isolated context:
|
||||
|
||||
```swift
|
||||
@MainActor
|
||||
class WindowStyler {
|
||||
nonisolated init(name: String) {
|
||||
self.primaryStyleName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Non-Isolated Deinitialization
|
||||
|
||||
Even if a type has actor isolation, deinitializers are _always_ non-isolated:
|
||||
|
||||
```swift
|
||||
actor BackgroundStyler {
|
||||
private let store = StyleStore()
|
||||
|
||||
deinit {
|
||||
Task { [store] in
|
||||
await store.stopNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Important: **Never** extend the life-time of `self` from within `deinit`.
|
||||
|
||||
# Migration Strategy
|
||||
|
||||
Get started migrating your project to the Swift 6 language mode.
|
||||
|
||||
When faced with a large number of problems, **don't panic.**
|
||||
Frequently, you'll find yourself making substantial progress with just a few changes.
|
||||
|
||||
## Strategy
|
||||
|
||||
The approach has three key steps:
|
||||
|
||||
- Select a module
|
||||
- Enable stricter checking with Swift 5
|
||||
- Address warnings
|
||||
|
||||
This process will be inherently _iterative_.
|
||||
|
||||
## Begin from the Outside
|
||||
|
||||
It can be easier to start with the outer-most root module in a project.
|
||||
Changes here can only have local effects, making it possible to keep work contained.
|
||||
|
||||
## Use the Swift 5 Language Mode
|
||||
|
||||
It is possible to incrementally enable more of the Swift 6 checking mechanisms while remaining in Swift 5 mode.
|
||||
This will surface issues only as warnings.
|
||||
|
||||
To start, enable a single upcoming concurrency feature:
|
||||
|
||||
Proposal | Description | Feature Flag
|
||||
:-----------|-------------|-------------
|
||||
[SE-0401][] | Remove Actor Isolation Inference caused by Property Wrappers | `DisableOutwardActorInference`
|
||||
[SE-0412][] | Strict concurrency for global variables | `GlobalConcurrency`
|
||||
[SE-0418][] | Inferring `Sendable` for methods and key path literals | `InferSendableFromCaptures`
|
||||
|
||||
[SE-0401]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md
|
||||
[SE-0412]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0412-strict-concurrency-for-global-variables.md
|
||||
[SE-0418]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md
|
||||
|
||||
After you have addressed issues uncovered by upcoming feature flags, enable complete checking for the module.
|
||||
|
||||
## Address Warnings
|
||||
|
||||
There is one guiding principle: **express what is true now**.
|
||||
Resist the urge to refactor your code to address issues.
|
||||
|
||||
# Enabling Complete Concurrency Checking
|
||||
|
||||
Incrementally address data-race safety issues by enabling diagnostics as warnings in your project.
|
||||
|
||||
## Using the Swift compiler
|
||||
|
||||
```
|
||||
~ swift -strict-concurrency=complete main.swift
|
||||
```
|
||||
|
||||
## Using SwiftPM
|
||||
|
||||
### Command-line invocation
|
||||
|
||||
```
|
||||
~ swift build -Xswiftc -strict-concurrency=complete
|
||||
~ swift test -Xswiftc -strict-concurrency=complete
|
||||
```
|
||||
|
||||
### Package manifest
|
||||
|
||||
With Swift 5.9 or Swift 5.10 tools:
|
||||
|
||||
```swift
|
||||
.target(
|
||||
name: "MyTarget",
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency")
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
When using Swift 6.0 tools or later:
|
||||
|
||||
```swift
|
||||
.target(
|
||||
name: "MyTarget",
|
||||
swiftSettings: [
|
||||
.enableUpcomingFeature("StrictConcurrency")
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
## Using Xcode
|
||||
|
||||
Set the "Strict Concurrency Checking" setting to "Complete" in the Xcode build settings.
|
||||
|
||||
# Enabling The Swift 6 Language Mode
|
||||
|
||||
Guarantee your code is free of data races by enabling the Swift 6 language mode.
|
||||
|
||||
## Using the Swift compiler
|
||||
|
||||
```
|
||||
~ swift -swift-version 6 main.swift
|
||||
```
|
||||
|
||||
## Using SwiftPM
|
||||
|
||||
### Package manifest
|
||||
|
||||
A `Package.swift` file that uses `swift-tools-version` of `6.0` will enable the Swift 6 language mode for all targets:
|
||||
|
||||
```swift
|
||||
// swift-tools-version: 6.0
|
||||
|
||||
let package = Package(
|
||||
name: "MyPackage",
|
||||
targets: [
|
||||
.target(name: "FullyMigrated"),
|
||||
.target(
|
||||
name: "NotQuiteReadyYet",
|
||||
swiftSettings: [
|
||||
.swiftLanguageMode(.v5)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
## Using Xcode
|
||||
|
||||
Set the "Swift Language Version" setting to "6" in the Xcode build settings.
|
||||
|
||||
# Incremental Adoption
|
||||
|
||||
Learn how you can introduce Swift concurrency features into your project incrementally.
|
||||
|
||||
## Wrapping Callback-Based Functions
|
||||
|
||||
APIs that accept and invoke a single function on completion are an extremely common pattern in Swift.
|
||||
You can wrap this function up into an asynchronous version using _continuations_:
|
||||
|
||||
```swift
|
||||
func updateStyle(backgroundColor: ColorComponents) async {
|
||||
await withCheckedContinuation { continuation in
|
||||
updateStyle(backgroundColor: backgroundColor) {
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: You have to take care to _resume_ the continuation _exactly once_.
|
||||
|
||||
## Dynamic Isolation
|
||||
|
||||
Dynamic isolation provides runtime mechanisms you can use as a fallback for describing data isolation.
|
||||
It can be an essential tool for interfacing a Swift 6 component with another that has not yet been updated.
|
||||
|
||||
### Preconcurrency
|
||||
|
||||
You can stage in diagnostics caused by adding global actor isolation on a protocol using `@preconcurrency`:
|
||||
|
||||
```swift
|
||||
@preconcurrency @MainActor
|
||||
protocol Styler {
|
||||
func applyStyle()
|
||||
}
|
||||
```
|
||||
|
||||
### Assume Isolated
|
||||
|
||||
When you know code is running on a specific actor but the compiler cannot verify this statically:
|
||||
|
||||
```swift
|
||||
func doSomething() {
|
||||
MainActor.assumeIsolated {
|
||||
// Code that requires MainActor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Runtime Behavior
|
||||
|
||||
Learn how Swift concurrency runtime semantics differ from other runtimes.
|
||||
|
||||
## Limiting concurrency using Task Groups
|
||||
|
||||
When dealing with a large list of work, avoid creating thousands of tasks at once:
|
||||
|
||||
```swift
|
||||
let lotsOfWork: [Work] = ...
|
||||
let maxConcurrentWorkTasks = min(lotsOfWork.count, 10)
|
||||
|
||||
await withTaskGroup(of: Something.self) { group in
|
||||
var submittedWork = 0
|
||||
for _ in 0..<maxConcurrentWorkTasks {
|
||||
group.addTask {
|
||||
await lotsOfWork[submittedWork].work()
|
||||
}
|
||||
submittedWork += 1
|
||||
}
|
||||
|
||||
for await result in group {
|
||||
process(result)
|
||||
|
||||
if submittedWork < lotsOfWork.count,
|
||||
let remainingWorkItem = lotsOfWork[submittedWork] {
|
||||
group.addTask {
|
||||
await remainingWorkItem.work()
|
||||
}
|
||||
submittedWork += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Source Compatibility
|
||||
|
||||
Swift 6 includes a number of evolution proposals that could potentially affect source compatibility.
|
||||
These are all opt-in for the Swift 5 language mode.
|
||||
|
||||
## Key Changes
|
||||
|
||||
- **NonfrozenEnumExhaustivity**: Lack of a required `@unknown default` has changed from a warning to an error
|
||||
- **StrictConcurrency**: Will introduce errors for any code that risks data races
|
||||
- **DeprecateApplicationMain**: Will introduce an error for any code that has not migrated to using `@main`
|
||||
|
||||
For a complete list of source compatibility changes, consult the Swift Evolution proposals.
|
||||
Reference in New Issue
Block a user