Introduktion: Varför Swift 6.2 förändrar spelplanen
Swift 6.2, som lanserades tillsammans med Xcode 26 under WWDC25, är utan tvekan den mest betydelsefulla förändringen i Swifts concurrency-modell sedan async/await först dök upp. Det handlar inte om några nya funktioner eller smärre justeringar — det här är en genomgripande filosofisk omsvängning i hur vi Swift-utvecklare förväntas tänka kring samtidig programmering.
Och ärligt talat? Det var på tiden.
I hjärtat av förändringen ligger konceptet "Approachable Concurrency", eller tillgänglig concurrency på svenska. Grundtanken är enkel men ganska revolutionerande: den absoluta merparten av kod i en typisk mobilapplikation behöver inte concurrency överhuvudtaget. En vanlig iOS-app är i grunden ett entrådigt program som hanterar användargränssnitt på huvudtråden och emellanåt gör nätverksanrop eller tunga beräkningar i bakgrunden. Ändå tvingade Swifts tidigare concurrency-modell oss att förstå komplexa begrepp som Sendable-kompatibilitet, aktörisolering och datakapplöpssäkerhet — även om vi i praktiken inte skrev någon parallell kod alls.
Swift-teamet publicerade i februari 2025 ett visionsdokument med titeln "Improving the Approachability of Data-Race Safety", där de presenterade principen om progressive disclosure — stegvis avslöjande. Tanken är att du bara behöver förstå så mycket om concurrency som du faktiskt använder. En nybörjare ska kunna skriva fungerande Swift-kod utan att behöva förstå aktörer, medan en erfaren utvecklare kan finslipa prestanda med explicit parallellism — båda utan onödiga varningar eller hinder.
I den här guiden går vi igenom alla viktiga förändringar i Swift 6.2:s concurrency-modell: från nonisolated(nonsending) som standardbeteende, till den nya @concurrent-attributen, till defaultIsolation(MainActor.self) och hur du aktiverar allt i dina befintliga projekt. Så, låt oss dyka in.
Bakgrund: Problemet med Swift 6 Concurrency
För att verkligen förstå varför Swift 6.2:s förändringar är så viktiga behöver vi först titta på vad som gick snett med den ursprungliga concurrency-modellen i Swift 6. Problemet var inte att modellen var felaktig i teorin — tvärtom var den tekniskt elegant. Problemet var att den var överväldigande för de flesta av oss i praktiken.
Varningsstormen i Swift 6
När Swift 6.0 aktiverade fullständig strikt concurrency-kontroll (strict concurrency checking), mötte många projekt en veritabel tsunami av varningar och fel. Även om koden var helt korrekt och i praktiken entrådad, krävde kompilatorn Sendable-markeringar, @MainActor-annotationer och explicit aktörisolering överallt.
Utvecklare som bara ville uppgradera till den senaste Swift-versionen fick lägga dagar på att åtgärda kompilatorvarningar — utan att kodens funktionalitet ändrades det minsta. Frustrerande, minst sagt.
Sendable-problemet
Sendable-protokollets syfte var att säkerställa att typer säkert kunde skickas över aktörgränser. I praktiken ledde detta till en situation där nästan varje typ antingen behövde uttryckligen uppfylla Sendable-kravet eller markeras med @unchecked Sendable, vilket i praktiken stängde av hela säkerhetskontrollen. Tredjepartsbibliotek som inte hade uppdaterats var särskilt jobbiga, eftersom du som utvecklare inte hade någon möjlighet att fixa situationen utom att vänta på en biblioteksuppdatering.
Det förvirrande beteendet hos nonisolated async
En av de mest förvirrande aspekterna (och det säger en del) var hur nonisolated async-funktioner betedde sig. När en sådan funktion anropades från en aktör, lämnade den tyst anroparens aktörkontekst och kördes på en annan tråd. Det här ledde till subtila och svårlokaliserade buggar, särskilt med UI-uppdateringar:
@MainActor
class ViewModel {
var statusText = "Laddar..."
func updateStatus() async {
// Vi är på MainActor här
statusText = "Hämtar data..."
// Denna funktion lämnar TYST MainActor!
let result = await fetchData()
// Vi är tillbaka på MainActor här, men vad hände däremellan?
statusText = result
}
// I Swift 6: Denna funktion kör på en ANNAN tråd
// trots att den anropas från MainActor
nonisolated func fetchData() async -> String {
// Kör INTE på MainActor - överraskande för många!
return "Data hämtad"
}
}
Denna tysta trådväxling var kanske den största enskilda källan till förvirring. Vi förväntar oss naturligt att funktioner som anropas i sekvens körs på samma tråd, precis som i all annan programmering. Att en funktion utan någon explicit bakgrundsmarkering plötsligt lämnade huvudtråden var kontraintuitivt och ledde till datakapplöp som var riktigt jobbiga att debugga.
Klyftan mellan enkelt och parallellt
Det kanske största problemet var att steget från enkel synkron kod till concurrent kod krävde att man förstod hela concurrency-modellen på en gång. Det fanns ingen naturlig mellannivå. Antingen skrev du helt synkron kod eller så behövde du förstå aktörer, Sendable, isolering och trådsäkerhet i sin helhet. Den klyftan avskräckte särskilt nybörjare och gjorde Swift mer otillgängligt än det borde ha varit.
Vad är Approachable Concurrency?
Approachable Concurrency, eller tillgänglig concurrency, är inte en enstaka funktion utan en samling förändringar som tillsammans omformar hur vi interagerar med concurrency i Swift. Den grundläggande filosofin är progressive disclosure — stegvis avslöjande av komplexitet. Du börjar enkelt och opterar in i mer avancerade möjligheter när du faktiskt behöver dem.
Den trestegiga modellen
Swift 6.2:s concurrency-modell följer tre tydliga nivåer:
- Enkel entrådad kod: Allting kör på huvudtråden som standard. Du behöver inte tänka på concurrency alls.
- Asynkron kod utan datakapplöp: Du använder
async/awaitför nätverksanrop och liknande, men funktionerna stannar kvar på anroparens aktör. Ingen oväntad trådväxling. - Explicit parallellism: När du aktivt vill köra kod på en bakgrundstråd markerar du det uttryckligen med
@concurrent. Först då behöver du förståSendableoch aktörgränser.
Det fina med den här modellen är att varje nivå bygger naturligt på den föregående. Du behöver inte lära dig nivå tre förrän du faktiskt stöter på ett problem som kräver det.
Två nyckelpelare
Hela Approachable Concurrency vilar på två huvudsakliga förändringar:
- NonisolatedNonsendingByDefault: Asynkrona funktioner ärver anroparens isoleringskontekst istället för att lämna den. Det innebär att en
async-funktion som anropas frånMainActorstannar påMainActorom inget annat anges. - defaultIsolation(MainActor.self): Hela modulen kan sätta
MainActorsom standardisolering, vilket innebär att all kod implicit kör på huvudtråden. Perfekt för UI-tunga applikationer.
Dessa två förändringar samverkar för att skapa en mental modell där allt är entrådigt som standard och parallellism är något du aktivt väljer. Det är en fundamentalt annorlunda upplevelse jämfört med Swift 6, där concurrency var något du aktivt behövde skydda dig emot.
nonisolated(nonsending): Asynkrona funktioner som stannar kvar
Den första och kanske viktigaste pelaren i Approachable Concurrency är förändringen av hur nonisolated async-funktioner beter sig. Förändringen definieras i SE-0461 och innebär att standardbeteendet för asynkrona funktioner ändras i grunden.
Före: Funktioner som tyst lämnade aktören
I Swift 6.0 innebar en nonisolated async-funktion att den uttryckligen lämnade anroparens aktörkontekst och kördes på en godtycklig executor. Det var standardbeteendet — en asynkron funktion utan explicit aktörisolering flyttade automatiskt till en bakgrundstråd:
// Swift 6.0 - Före Approachable Concurrency
@MainActor
class DataManager {
var items: [String] = []
func refresh() async {
// Vi är på MainActor
let newItems = await loadItems()
// Tillbaka på MainActor
items = newItems
}
// Denna funktion LÄMNAR MainActor tyst!
nonisolated func loadItems() async -> [String] {
// Kör på en bakgrundstråd - inte MainActor
// Även om vi bara gör enkla beräkningar
return ["Objekt 1", "Objekt 2", "Objekt 3"]
}
}
Efter: Funktioner som ärver anroparens kontext
Med NonisolatedNonsendingByDefault aktiverat ändras standardbeteendet fundamentalt. Nu ärver en nonisolated async-funktion anroparens isoleringskontekst. Anropas den från MainActor? Då kör den på MainActor. Anropas den från en annan aktör? Då kör den där. Utan isoleringskontekst? Generella executorn:
// Swift 6.2 - Med Approachable Concurrency
class DataManager {
var items: [String] = []
func refresh() async {
// Vi är på MainActor (tack vare defaultIsolation)
let newItems = await loadItems()
// Fortfarande på MainActor
items = newItems
}
// Denna funktion STANNAR på anroparens aktör!
func loadItems() async -> [String] {
// Kör på MainActor om anropad från MainActor
// Kör på bakgrundstråd om anropad därifrån
return ["Objekt 1", "Objekt 2", "Objekt 3"]
}
}
Märk skillnaden? Det är subtilt men enormt viktigt. Funktionen följer med anroparen istället för att smita iväg på egen hand.
Hur det fungerar tekniskt
Under huven innebär nonisolated(nonsending) att funktionen får ett implicit isoleringsparameter. Kompilatorn känner av vilken isoleringskontekst anroparen befinner sig i och skickar med den till funktionen. Funktionen kör sedan på samma executor som anroparen, utan någon trådväxling.
I praktiken betyder det:
- Funktionen blockerar inte anroparens tråd i traditionell mening, men den kör på samma executor.
- Om anroparen är på
MainActorkör funktionen påMainActor. - Om anroparen saknar isolering kör funktionen på den generella concurrent executorn.
- Det finns ingen automatisk trådväxling om du inte uttryckligen begär en.
Aktivera i ditt projekt
För att aktivera detta beteende lägger du till funktionsflaggan NonisolatedNonsendingByDefault i din Package.swift:
.target(
name: "MinApp",
swiftSettings: [
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
]
)
I Xcode hittar du inställningen under Build Settings genom att söka efter "Upcoming Features" och lägga till NonisolatedNonsendingByDefault i listan. I nya projekt skapade med Xcode 26 är flaggan aktiverad som standard.
Värt att notera: när flaggan är aktiverad gäller det nya beteendet för hela modulen. Befintlig kod som förlitade sig på att nonisolated async-funktioner lämnade aktören kan behöva uppdateras. Vi återkommer till migrationsstrategier lite längre fram.
@concurrent: Explicit parallellism
Om nonisolated(nonsending) är ena sidan av myntet — funktioner som stannar på anroparens aktör — så är den nya @concurrent-attributen den andra. När du faktiskt vill att kod ska köras utanför anroparens aktörkontekst, till exempel för tunga beräkningar eller nätverksanrop som inte ska blockera huvudtråden, markerar du funktionen med @concurrent.
När ska du använda @concurrent?
Använd @concurrent när du har arbete som:
- Är beräkningsmässigt tungt och inte bör blockera UI-tråden
- Innefattar långa I/O-operationer som nätverksanrop eller filhantering
- Kan och bör köras parallellt med annan kod
- Inte direkt interagerar med UI-komponenter
Grundläggande syntax
Attributen @concurrent gör automatiskt funktionen nonisolated, vilket innebär att den lämnar anroparens aktörkontekst och kör på den generella concurrent executorn:
@MainActor
class NetworkingClient {
@concurrent
nonisolated func loadData() async throws -> [Item] {
let url = URL(string: "https://api.example.com/items")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Item].self, from: data)
}
}
Här är NetworkingClient isolerad till MainActor, men loadData() kör uttryckligen på en bakgrundstråd tack vare @concurrent. När funktionen returnerar och resultatet används på MainActor måste returtypen vara Sendable — i det här fallet är [Item] det om Item uppfyller Sendable.
Skillnaden mot det gamla beteendet
Viktigt att förstå: @concurrent ger dig i stort sett samma beteende som det gamla standardbeteendet för nonisolated async-funktioner i Swift 6.0. Skillnaden är att det nu är ett medvetet val snarare än något som bara hände. Du signalerar tydligt: "Ja, jag vill att den här funktionen kör på en annan tråd, och jag har tänkt igenom konsekvenserna."
Det är en stor skillnad i läsbarhet och intention.
Kombination med nätverkslogik
Här är ett mer komplett exempel som visar hur @concurrent används i en typisk nätverksklass:
class APIService {
private let baseURL = URL(string: "https://api.example.com")!
@concurrent
nonisolated func fetchUsers() async throws -> [User] {
let url = baseURL.appendingPathComponent("users")
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw APIError.invalidResponse
}
return try JSONDecoder().decode([User].self, from: data)
}
@concurrent
nonisolated func downloadImage(from urlString: String) async throws -> Data {
guard let url = URL(string: urlString) else {
throw APIError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
Båda funktionerna är markerade med @concurrent eftersom de gör nätverksanrop som kan ta tid och inte bör blockera huvudtråden. Returvärdet skickas sedan tillbaka till anroparen, som typiskt befinner sig på MainActor och uppdaterar gränssnittet.
Default Actor Isolation: MainActor som standard
Den andra stora pelaren i Approachable Concurrency är möjligheten att sätta MainActor som standardisolering för en hel modul. Det definieras i SE-0466 och innebär att all kod i modulen implicit behandlas som om den vore markerad med @MainActor, om inget annat anges.
Varför MainActor som standard?
Tänk efter: för de flesta iOS- och macOS-applikationer är huvudtråden där nästan allt arbete sker. UI-uppdateringar, händelsehantering, databindning — allt detta måste ske på huvudtråden. Ändå krävde Swift 6.0 att vi explicit markerade varje klass, struct och funktion som interagerade med UI med @MainActor. Resultatet var kod översållad med annotationer som i grund och botten bara sa "ja, det här kör på huvudtråden, som det alltid gjort".
Med defaultIsolation(MainActor.self) vänder vi detta: allt kör på huvudtråden om du inte aktivt väljer något annat. Det stämmer överens med hur de flesta appar faktiskt fungerar och eliminerar behovet av otaliga @MainActor-annotationer.
Aktivering i Package.swift
För att aktivera standardisolering med MainActor kombinerar du den med NonisolatedNonsendingByDefault i din Package.swift:
.target(
name: "MinApp",
swiftSettings: [
.defaultIsolation(MainActor.self),
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
]
)
Aktivering i Xcode Build Settings
I Xcode 26 hittar du inställningen under projektets Build Settings. Sök efter "Default Actor Isolation" och sätt värdet till MainActor. För befintliga projekt som uppgraderar till Xcode 26 är detta inte automatiskt aktiverat — du behöver sätta det manuellt.
Vad innebär det i praktiken?
När standardisoleringen är aktiv försvinner behovet av explicita @MainActor-annotationer för det mesta av din kod:
// Med defaultIsolation(MainActor.self) aktiverat
// Ingen @MainActor behövs - allt är implicit på MainActor!
class SettingsManager {
var theme: Theme = .light
var fontSize: Int = 14
var notificationsEnabled = true
func updateTheme(_ newTheme: Theme) {
theme = newTheme
// Säker UI-uppdatering - vi är på MainActor
NotificationCenter.default.post(name: .themeChanged, object: nil)
}
}
struct SettingsView: View {
@State private var manager = SettingsManager()
var body: some View {
Form {
Picker("Tema", selection: $manager.theme) {
Text("Ljust").tag(Theme.light)
Text("Mörkt").tag(Theme.dark)
}
Toggle("Aviseringar", isOn: $manager.notificationsEnabled)
}
}
}
Jämför detta med hur samma kod skulle behöva se ut utan standardisolering, där både SettingsManager och dess egenskaper behövde @MainActor-annotationer. Den kodmässiga minskningen är inte bara kosmetisk — den minskar den kognitiva bördan avsevärt och gör koden lättare att läsa och underhålla.
nonisolated-nyckelordet fungerar fortfarande för att uttryckligen exkludera specifika typer eller funktioner från standardisoleringen. Har du en ren datatyp som inte behöver vara på MainActor? Markera den som nonisolated:
// Explicit undantag från standardisoleringen
nonisolated struct MathHelper {
static func calculateAverage(_ numbers: [Double]) -> Double {
numbers.reduce(0, +) / Double(numbers.count)
}
}
Aktivera Approachable Concurrency i Xcode
Att aktivera Approachable Concurrency i ditt projekt beror på om det är ett nytt eller befintligt projekt. Låt oss gå igenom båda scenarierna.
För nya projekt
Skapar du ett nytt projekt i Xcode 26? De goda nyheterna: de flesta Approachable Concurrency-funktioner är aktiverade som standard. Xcode 26 konfigurerar automatiskt NonisolatedNonsendingByDefault och defaultIsolation(MainActor.self) åt dig. Bara börja koda, helt enkelt.
För befintliga projekt
För befintliga projekt är situationen lite mer komplex (men fullt hanterbar). Swift 6.2 introducerar flera funktionsflaggor som du kan aktivera individuellt. Här är de viktigaste:
- NonisolatedNonsendingByDefault: Ändrar standardbeteendet så att
nonisolated async-funktioner ärver anroparens isolering. Klart mest påverkande. - DisableOutwardActorInference: Förhindrar att aktörisolering sprider sig "utåt" från en typ till en annan på oväntade sätt.
- GlobalActorIsolatedTypesUsability: Förbättrar användarvänligheten för typer isolerade till globala aktörer, särskilt i generiska sammanhang.
- InferIsolatedConformances: Låter kompilatorn automatiskt härleda isoleringskonformiteter, vilket minskar behovet av explicita annotationer vid protokollimplementationer.
- InferSendableFromCaptures: Kompilatorn kan automatiskt avgöra om en closure är
Sendablebaserat på vad den fångar, istället för att kräva explicita markeringar.
Rekommenderad migrationsordning
Jag rekommenderar starkt att aktivera flaggorna en i taget snarare än alla på en gång. Här är ordningen jag föreslår:
- Börja med
InferSendableFromCaptures— den minskar antaletSendable-relaterade varningar och är normalt den minst disruptiva. - Lägg sedan till
DisableOutwardActorInference— den kan avslöja dolda isoleringsproblem men ger sällan många nya fel. - Aktivera
GlobalActorIsolatedTypesUsabilityochInferIsolatedConformances— dessa förbättrar ergonomin utan att typiskt introducera nya fel. - Aktivera
NonisolatedNonsendingByDefault— den mest omfattande flaggan, aktivera sist efter att du kontrollerat att din kod fungerar med de övriga. - Slutligen, när allt fungerar, aktivera
defaultIsolation(MainActor.self)— nu kan du ta bort massor av explicita@MainActor-annotationer.
Konfiguration i Package.swift
Så här ser den fullständiga konfigurationen ut när alla flaggor är aktiverade:
.target(
name: "MinApp",
swiftSettings: [
.defaultIsolation(MainActor.self),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("DisableOutwardActorInference"),
.enableUpcomingFeature("GlobalActorIsolatedTypesUsability"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("InferSendableFromCaptures")
]
)
Kör dina tester och verifiera att allt fungerar korrekt efter varje ny flagga. Stöter du på kompilatorfel? Åtgärda dem innan du går vidare till nästa. Det systematiska tillvägagångssättet sparar enormt med tid och frustration jämfört med att försöka aktivera allt på en gång.
Praktiskt exempel: Migrera en befintlig app
Nog med teori — låt oss titta på ett konkret, praktiskt exempel på hur Approachable Concurrency påverkar riktig kod. Vi tar en vanlig ViewModel-klass och visar hur den ser ut före och efter migreringen.
Före: Med Swift 6.0 concurrency
Här är en typisk ViewModel för en fotoapplikation, skriven med Swift 6.0:s concurrency-modell. Notera alla annotationer som krävs:
@MainActor
class PhotoViewModel: ObservableObject {
@Published var photos: [Photo] = []
@Published var isLoading = false
func loadPhotos() async {
isLoading = true
do {
let photos = try await fetchPhotos()
self.photos = photos
} catch {
print("Fel: \(error)")
}
isLoading = false
}
nonisolated func fetchPhotos() async throws -> [Photo] {
let url = URL(string: "https://api.example.com/photos")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Photo].self, from: data)
}
}
I den här versionen måste vi uttryckligen markera klassen med @MainActor för att UI-uppdateringar ska ske på rätt tråd. Funktionen fetchPhotos() är nonisolated för att den ska kunna köras på en bakgrundstråd — men det finns inget i koden som tydligt kommunicerar varför funktionen lämnar MainActor.
Efter: Med Approachable Concurrency
Med både defaultIsolation(MainActor.self) och NonisolatedNonsendingByDefault aktiverade blir samma ViewModel mycket renare:
class PhotoViewModel: ObservableObject {
@Published var photos: [Photo] = []
@Published var isLoading = false
func loadPhotos() async {
isLoading = true
do {
let photos = try await fetchPhotos()
self.photos = photos
} catch {
print("Fel: \(error)")
}
isLoading = false
}
@concurrent
nonisolated func fetchPhotos() async throws -> [Photo] {
let url = URL(string: "https://api.example.com/photos")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Photo].self, from: data)
}
}
Vad har ändrats?
Låt oss bryta ner det:
@MainActorpå klassen är borta: Tack varedefaultIsolation(MainActor.self)är klassen implicit isolerad till MainActor. Ingen annotation behövs.@concurrentpåfetchPhotos(): Istället för en implicitnonisolatedsom tyst lämnar MainActor har vi nu en uttrycklig@concurrent-markering som tydligt säger: "Den här funktionen ska köras på en bakgrundstråd." Kristallklart.- Resten av koden är identisk:
loadPhotos()behöver inga annotationer alls. Den kör på MainActor, anroparfetchPhotos()som kör på en bakgrundstråd, och uppdaterar sedan UI-egenskaperna tillbaka på MainActor.
Ett större exempel med flera ViewModels
I en större applikation blir fördelarna ännu tydligare. Här är ett mer realistiskt scenario med flera samverkande klasser:
// Ingen @MainActor behövs någonstans - allt är implicit MainActor
class AppState: ObservableObject {
@Published var currentUser: User?
@Published var isAuthenticated = false
func login(email: String, password: String) async {
do {
let user = try await AuthService.shared.authenticate(
email: email, password: password
)
currentUser = user
isAuthenticated = true
} catch {
print("Inloggning misslyckades: \(error)")
}
}
}
class AuthService {
static let shared = AuthService()
@concurrent
nonisolated func authenticate(
email: String,
password: String
) async throws -> User {
let url = URL(string: "https://api.example.com/auth")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try JSONEncoder().encode(
LoginRequest(email: email, password: password)
)
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(User.self, from: data)
}
}
class PhotoViewModel: ObservableObject {
@Published var photos: [Photo] = []
@Published var isLoading = false
@Published var errorMessage: String?
func loadPhotos() async {
isLoading = true
errorMessage = nil
do {
photos = try await PhotoService.shared.fetchPhotos()
} catch {
errorMessage = "Kunde inte hämta foton: \(error.localizedDescription)"
}
isLoading = false
}
}
class PhotoService {
static let shared = PhotoService()
@concurrent
nonisolated func fetchPhotos() async throws -> [Photo] {
let url = URL(string: "https://api.example.com/photos")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Photo].self, from: data)
}
}
I det här större exemplet finns det inte en enda @MainActor-annotation, och ändå är all UI-relaterad kod säkert isolerad till huvudtråden. Nätverksanropen i AuthService och PhotoService är tydligt markerade med @concurrent, vilket gör det uppenbart vilken kod som kör i bakgrunden. Riktigt elegant, om du frågar mig.
Vanliga fallgropar och tips
Även om Approachable Concurrency gör livet mycket enklare finns det fortfarande saker att tänka på. Här är de vanligaste fallgroparna och hur du undviker dem.
Markera inte allt med @concurrent
Det är frestande att markera många funktioner med @concurrent, särskilt om du är van vid det gamla beteendet där asynkrona funktioner automatiskt kördes på bakgrundstråden. Men gör inte det — det motverkar hela syftet med Approachable Concurrency. Använd @concurrent bara när du har ett tydligt skäl att flytta arbete från huvudtråden. För de flesta funktioner är standardbeteendet — att stanna på anroparens aktör — exakt rätt.
nonisolated(nonsending) blockerar anroparens executor
Kom ihåg att när en nonisolated(nonsending)-funktion kör på anroparens aktör så upptar den den aktörens executor. Har du en lång synkron beräkning inne i en async-funktion som kör på MainActor? Då kan gränssnittet frysa. Om du märker att det händer är det en tydlig signal att funktionen bör vara @concurrent istället.
Prestandaöverväganden
Standardisoleringen till MainActor innebär att mer kod kör på huvudtråden än tidigare. För de allra flesta applikationer är det inte ett problem — moderna enheter är snabba och den mesta applogiken är lättviktig. Men har du beräkningstung logik (bildbehandling, kryptering, stora dataomvandlingar) bör du uttryckligen flytta den till en bakgrundstråd med @concurrent.
Mitt tips: använd Instruments för att profilera din app och identifiera faktiska flaskhalsar istället för att gissa. Prematur optimering är fortfarande roten till allt ont.
Migrera en flagga i taget
Det här tål att upprepas: aktivera funktionsflaggorna en i taget och kör hela din testsvit efter varje ändring. Det systematiska tillvägagångssättet gör det mycket lättare att identifiera och åtgärda problem än att aktivera allt på en gång och ställas inför dussintals kompilatorfel.
Tredjepartsbibliotek
Var medveten om att tredjepartsbibliotek du använder kanske inte är uppdaterade för Approachable Concurrency ännu. Dina inställningar gäller bara för din egen modul. Interaktionen med äldre bibliotek fungerar generellt bra, men du kan behöva skriva bryggor eller adaptrar i vissa fall.
Sammanfattning
Swift 6.2:s Approachable Concurrency representerar en mognadsprocess för Swifts concurrency-modell. Istället för att tvinga alla utvecklare att handskas med fullständig concurrency-säkerhet från dag ett, erbjuder Swift nu en stegvis väg från enkel, entrådad kod till avancerad parallellism.
Den mentala modellen är nu kristallklar: allt är entrådigt som standard, och parallellism är något du uttryckligen väljer. När du skriver en funktion kör den på samma tråd som anroparen. När du vill att den ska köra i bakgrunden markerar du det med @concurrent. Det är allt du behöver veta för att komma igång.
De två nyckelåtgärderna för att ta del av dessa förbättringar:
- Aktivera
NonisolatedNonsendingByDefaultför att få det nya beteendet där asynkrona funktioner ärver anroparens isolering. - Aktivera
defaultIsolation(MainActor.self)för att slippa explicita@MainActor-annotationer i UI-tunga appar.
Swift-teamets arbete med tillgänglig concurrency visar att de lyssnar på sin utvecklarcommunity. Resultatet är en concurrency-modell som inte bara är säker, utan också begriplig — och det är det som i slutändan avgör om utvecklare faktiskt använder den. Swift 6.2 gör concurrent programmering tillgänglig för alla, oavsett erfarenhetsnivå, och det är precis så det borde vara.