Johdanto: Miksi Swift 6.2 on iso juttu
Swift 6.2 julkaistiin yhdessä Xcode 26:n ja iOS 26:n kanssa WWDC25-tapahtumassa, ja rehellisesti sanottuna — tämä on merkittävin muutos Swiftin rinnakkaisuusmallissa sitten async/await-syntaksin käyttöönoton. Tämä ei ole mikään pieni päivitys tai muutama uusi ominaisuus. Kyseessä on kokonaisvaltainen filosofinen suunnanmuutos.
Muutoksen ytimessä on käsite nimeltä "lähestyttävä rinnakkaisuus" (Approachable Concurrency). Perusajatus on oikeastaan aika yksinkertainen: suurin osa mobiilisovelluksen koodista ei tarvitse rinnakkaisuutta lainkaan.
Mieti hetki tyypillistä iOS-sovellusta. Se on pohjimmiltaan yksisäikeinen ohjelma, joka suorittaa käyttöliittymälogiikkaa pääsäikeessä ja silloin tällöin tekee verkkokutsuja tai raskaita laskentaoperaatioita taustalla. Silti Swiftin aiempi rinnakkaisuusmalli pakotti kehittäjät ymmärtämään monimutkaisia käsitteitä kuten Sendable-yhteensopivuus, aktorieristys ja tietokilpailuturvallisuus — vaikka he eivät varsinaisesti kirjoittaneet rinnakkaista koodia. Turhauttavaa, eikö?
Swift-tiimi julkaisi helmikuussa 2025 visioasiakirjan nimeltä "Improving the Approachability of Data-Race Safety", jossa esiteltiin asteittaisen paljastamisen (progressive disclosure) periaate. Idea on yksinkertainen: kehittäjän tarvitsee ymmärtää rinnakkaisuudesta vain sen verran kuin hän todella käyttää. Aloittelija voi kirjoittaa toimivaa Swift-koodia ymmärtämättä aktoreita, ja kokenut kehittäjä voi hienosäätää suorituskykyä — kumpikin ilman turhia varoituksia.
Tässä oppaassa käymme läpi kaikki Swift 6.2:n tärkeimmät rinnakkaisuusparannukset: oletuseristyksen MainActorilla, nonisolated(nonsending)-oletuskäyttäytymisen, uuden @concurrent-attribuutin, Observations-tyypin sekä lukuisia pienempiä parannuksia. Eli sukellettaan sisään.
Miksi rinnakkaisuus on ollut niin tuskallista?
Ennen kuin pääsemme uutuuksiin, pieni katsaus taaksepäin. Jos olet kamppaillut Swiftin rinnakkaisuusvaroitusten kanssa, et ole yksin.
Varoitusten tulva Swift 6:ssa
Kun Swift 6.0 otti käyttöön täyden tietokilpailuturvallisuustarkistuksen, monet projektit joutuivat kohtaamaan satojen — jopa tuhansien — varoitusten ja virheiden vyöryn. Vaikka koodin logiikka oli täysin oikein ja käytännössä yksisäikeistä, kääntäjä vaati Sendable-merkintöjä, @MainActor-annotaatioita ja eksplisiittistä aktorieristystä kaikkialta.
Kehittäjät, jotka halusivat vain päivittää projektinsa uusimpaan Swift-versioon, saattoivat käyttää päiviä pelkkien kääntäjävaroitusten korjaamiseen — ilman että koodin toiminnallisuus muuttui millään tavalla. Ei kovin motivoivaa.
Sendable-yhteensopivuuden ongelmat
Sendable-protokollan tarkoitus oli varmistaa, että tyypit voidaan turvallisesti lähettää aktorirajojen yli. Käytännössä tämä johti kuitenkin tilanteeseen, jossa lähes jokainen tyyppi tarvitsi joko eksplisiittisen Sendable-vaatimuksen tai @unchecked Sendable -merkinnän, joka käytännössä ohitti koko turvallisuustarkistuksen. Kolmannen osapuolen kirjastot, joita ei ollut päivitetty, aiheuttivat erityisen paljon harmia — kehittäjällä ei yksinkertaisesti ollut muuta vaihtoehtoa kuin odottaa kirjaston päivitystä.
Aktorieristyksen monimutkaisuus
Aktorieristys oli konseptina looginen, mutta käytännössä? Vaikeaselkoinen. Kehittäjien piti ymmärtää, milloin funktio suoritetaan MainActorilla, milloin taustasäikeessä, milloin tarvitaan await ja milloin ei. Erityisen hämmentävää oli se, että nonisolated async -funktiot saattoivat yllättäen siirtyä pois kutsujansa aktorikontekstista. Tämä johti vaikeasti jäljitettäviin bugeihin käyttöliittymäpäivityksissä.
Kuilu yksinkertaisen ja rinnakkaisen koodin välillä
Ehkä suurin ongelma oli se, ettei ollut olemassa mitään helppoa välimuotoa. Joko kirjoitit täysin synkronista koodia tai jouduit ymmärtämään koko rinnakkaisuusmallin kaikkine vivahteissaan. Tämä kuilu pelotti erityisesti aloittelevia kehittäjiä.
Käytännössä monet tiimit päätyivät joko sivuuttamaan varoitukset kokonaan, käyttämään @unchecked Sendable -merkintöjä kaikkialla tai jäämään vanhempiin Swift-versioihin. Mikään näistä ei ollut hyvä ratkaisu. Swift-yhteisössä alkoi kuulua yhä äänekkäämpiä vaatimuksia siitä, että mallin täytyy muuttua.
Lähestyttävän rinnakkaisuuden kolmivaiheinen malli
Swift 6.2:n lähestyttävä rinnakkaisuus perustuu kolmivaiheiseen malliin, joka noudattaa asteittaisen paljastamisen periaatetta. Jokainen vaihe tuo lisää monimutkaisuutta, mutta vain silloin kun kehittäjä itse tarvitsee sitä. Tämä on se ratkaiseva ero.
Vaihe 1: Yksinkertainen yksisäikeinen koodi
Ensimmäisessä vaiheessa kirjoitat tavallista, peräkkäistä Swift-koodia. Oletuseristys MainActorilla tarkoittaa, että kaikki koodi suoritetaan pääsäikeessä — aivan kuten perinteisissä yksisäikeisissä ohjelmissa. Sinun ei tarvitse tietää mitään aktoreista, Sendable-protokollasta tai tietokilpailuturvallisuudesta. Koodi yksinkertaisesti toimii.
Vaihe 2: Asynkroninen koodi ilman tietokilpailuvirheitä
Toisessa vaiheessa alat käyttää async/await-syntaksia verkkokutsujen tekemiseen. nonisolated(nonsending)-oletuskäyttäytyminen varmistaa, että asynkroniset funktiot pysyvät kutsujansa aktorikontekstissa. Voit siis kirjoittaa async-koodia ilman huolta siitä, millä säikeellä se suoritetaan.
Vaihe 3: Suorituskyvyn optimointi rinnakkaisuudella
Vasta kolmannessa vaiheessa, kun haluat eksplisiittisesti siirtää raskasta laskentaa taustasäikeelle, käytät @concurrent-attribuuttia. Tässä vaiheessa sinun on ymmärrettävä Sendable-vaatimukset, mutta vain niiltä osin kuin se on kyseisen funktion kannalta välttämätöntä.
Tämä malli on käänteentekevä, koska se poistaa aiemman "kaikki tai ei mitään" -asetelman. Jokainen kehittäjä voi oppia rinnakkaisuutta omaan tahtiinsa.
Oletuseristys MainActorilla (SE-0466)
SE-0466 on yksi Swift 6.2:n merkittävimmistä muutoksista. Se mahdollistaa kokonaisen moduulin oletuseristyksen MainActorille — käytännössä kaikki moduulin tyypit ja funktiot saavat implisiittisen @MainActor-merkinnän, ellei toisin erikseen määritellä.
Miten se toimii käytännössä?
Kun oletuseristys on päällä, kääntäjä käsittelee jokaista tyyppiä ja funktiota moduulissa ikään kuin se olisi merkitty @MainActor-attribuutilla. Kaikki koodi suoritetaan pääsäikeessä, aivan kuten perinteisessä iOS-sovelluksessa ennen rinnakkaisuusmallien aikaa. Ja mikä parasta — tämä poistaa valtaosan niistä turhauttavista "vääristä positiivisista" varoituksista.
Käyttöönotto Package.swift-tiedostossa
Oletuseristyksen saa käyttöön lisäämällä .defaultIsolation(MainActor.self) kohteen swiftSettings-asetuksiin:
.target(
name: "MyApp",
swiftSettings: [
.defaultIsolation(MainActor.self)
]
)
Xcode 26 -projektissa sama asetus löytyy Build Settings -osiosta kohdasta "Default Actor Isolation", jonka voi asettaa arvoon MainActor.
Käytännön esimerkki
Kun oletuseristys on päällä, alla oleva koodi toimii ilman yhtään @MainActor-merkintää:
// Kaikki tässä moduulissa on implisiittisesti @MainActor
class ViewModel {
var items: [String] = []
var isLoading = false
func loadItems() async {
isLoading = true
let result = try? await fetchItems()
items = result ?? []
isLoading = false
}
}
struct ContentView: View {
@State private var viewModel = ViewModel()
var body: some View {
List(viewModel.items, id: \.self) { item in
Text(item)
}
.task {
await viewModel.loadItems()
}
}
}
Aiemmin tämä koodi olisi vaatinut eksplisiittisiä @MainActor-merkintöjä sekä ViewModel-luokalle että näkymälle. Nyt ne ovat implisiittisiä, ja koodi on puhtaampaa.
Eristyksestä poikkeaminen
Jos tarvitset tyypin, joka ei ole MainActor-eristetty, käytä nonisolated-avainsanaa:
// Tämä luokka ei ole MainActor-eristetty,
// vaikka oletuseristys on päällä
nonisolated class DataParser {
func parse(_ data: Data) -> [Item] {
// Tämä voidaan suorittaa millä tahansa säikeellä
return try! JSONDecoder().decode([Item].self, from: data)
}
}
Hyvä tietää: oletuseristys koskee vain sitä moduulia, jossa se on määritelty. Kolmannen osapuolen kirjastot säilyttävät oman eristystasonsa. Voit siis ottaa ominaisuuden käyttöön yksi moduuli kerrallaan — ei tarvitse tehdä kaikkea kerralla.
nonisolated(nonsending) oletusarvoisesti (SE-0461)
SE-0461 muuttaa perustavanlaatuisesti sitä, miten nonisolated async -funktiot käyttäytyvät. Tämä on ehkä teknisesti merkittävin muutos Swift 6.2:ssa, koska se korjaa yhden yleisimmistä virhelähteistä asynkronisessa koodissa.
Ongelma Swift 6.1:ssä
Aiemmin nonisolated async -funktiot suoritettiin aina globaalilla rinnakkaisella suorittimella, riippumatta siitä mistä kontekstista ne kutsuttiin. MainActorilla kutsuttu funktio siirtyi siis automaattisesti taustasäikeelle — usein kehittäjän tietämättä:
// Swift 6.1 -käyttäytyminen
@MainActor
class ViewModel {
func updateUI() async {
await fetchData() // ⚠️ Siirtyy pois MainActorilta!
}
nonisolated func fetchData() async {
// Suoritetaan taustasäikeessä
// MainActor.assertIsolated() // 💥 Kaatuu!
}
}
Kehittäjät olettivat luonnollisesti, että koska fetchData() on osa samaa luokkaa ja sitä kutsutaan MainActorilta, se myös suoritetaan MainActorilla. Mutta ei — funktio hyppäsi hiljaisesti taustasäikeelle. Tämä aiheutti vaikeasti löydettäviä bugeja erityisesti käyttöliittymäpäivityksissä.
Ratkaisu Swift 6.2:ssa
Swift 6.2:ssa nonisolated async -funktiot perivät oletusarvoisesti kutsujansa aktorikontekstin. Tätä kutsutaan nonisolated(nonsending)-käyttäytymiseksi:
// Swift 6.2 -käyttäytyminen
@MainActor
class ViewModel {
func updateUI() async {
await fetchData() // ✅ Pysyy MainActorilla
}
nonisolated func fetchData() async {
// Perii kutsujan kontekstin
MainActor.assertIsolated() // ✅ Toimii!
}
}
Paljon intuitiivisempaa. Funktio pysyy sillä aktorilla, jolta sitä kutsuttiin, ellei toisin erikseen määritellä.
Käyttöönotto
SE-0461 on saatavilla upcoming feature -lippuna:
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
Xcode 26:ssa tämä löytyy osana "Approachable Concurrency" -kääntäjäasetusta Build Settings -osiossa.
Huomio olemassa olevasta koodista
Tärkeä varoitus: tämä muutos voi vaikuttaa olemassa olevan koodin käyttäytymiseen. Jos sinulla on nonisolated async -funktioita, jotka on tarkoituksella suunniteltu taustasäikeelle, ne pysyvät nyt kutsujansa aktorilla. Merkitse ne silloin eksplisiittisesti @concurrent-attribuutilla.
@concurrent-attribuutti: Taustatyö selkeästi
@concurrent on Swift 6.2:n tapa sanoa: "haluan tämän funktion suoritettavan taustalla". Se on vastakohta uudelle nonisolated(nonsending)-oletuskäyttäytymiselle ja vastaa käytännössä Swift 6.1:n vanhaa nonisolated async -käyttäytymistä.
Milloin sitä tarvitaan?
@concurrent-attribuuttia käytetään, kun funktio tekee raskasta työtä, joka ei saa tukkia pääsäiettä. Tyypillisiä käyttötapauksia:
- Kuvankäsittely — filttereiden soveltaminen, koon muuttaminen
- JSON-dekoodaus — suurten datajoukkojen jäsentäminen
- Tiedosto-operaatiot — luku- ja kirjoitusoperaatiot
- Monimutkaiset laskennat — algoritmit, salaus, pakkaaminen
- Verkkokutsut — pitkäkestoiset lataukset tai lähetykset
Käytännön esimerkki
Katsotaan esimerkki kuvankäsittelystä taustasäikeellä:
class ImageProcessor {
@concurrent
nonisolated func processImage(_ data: Data) async throws -> UIImage {
// Suoritetaan rinnakkaisella säikeellä
let processed = try await applyFilters(to: data)
return processed
}
@concurrent
nonisolated func applyFilters(to data: Data) async throws -> UIImage {
// Raskas laskenta taustalla
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
let ciImage = CIImage(image: image)!
let filter = CIFilter(name: "CIPhotoEffectNoir")!
filter.setValue(ciImage, forKey: kCIInputImageKey)
guard let output = filter.outputImage else {
throw ImageError.filterFailed
}
let context = CIContext()
guard let cgImage = context.createCGImage(output, from: output.extent) else {
throw ImageError.renderFailed
}
return UIImage(cgImage: cgImage)
}
}
@MainActor
class PhotoViewModel: ObservableObject {
@Published var image: UIImage?
let processor = ImageProcessor()
func loadPhoto(from data: Data) async {
// processImage suoritetaan taustalla @concurrent ansiosta
if let result = try? await processor.processImage(data) {
self.image = result // Palaa automaattisesti MainActorille
}
}
}
Huomaa miten @concurrent ja nonisolated toimivat yhdessä. @concurrent kertoo kääntäjälle, että funktio suoritetaan erillisellä säikeellä. Kun loadPhoto kutsuu processImage-metodia, suoritus siirtyy automaattisesti taustasäikeelle, ja kun tulos palautetaan, palataan takaisin MainActorille. Kaikki tapahtuu automaattisesti await-avainsanan ansiosta.
Tämä malli on kriittinen käyttöliittymäsovelluksissa. Pääsäikeen tukkiminen raskaalla laskennalla johtaa hitauteen ja huonoon käyttäjäkokemukseen — ja käyttäjät huomaavat sen kyllä.
@concurrent vs. Task.detached
Aiemmin monet käyttivät Task.detached-kutsua siirtääkseen työn taustasäikeelle. @concurrent on eleganttimpi ratkaisu, koska se kuvaa funktion tarkoituksen suoraan sen määrittelyssä eikä kutsupaikassa. Parempi luettavuus, parempi ylläpidettävyys.
Observations: Uusi tapa seurata tilaa
Swift 6.2 tuo mukanaan uuden Observations-tyypin, joka on AsyncSequence-yhteensopiva rakenne @Observable-luokkien tilan seuraamiseen. Se korvaa monissa tapauksissa aiemman withObservationTracking-funktion ja tarjoaa huomattavasti paremman kehittäjäkokemuksen.
Perusesimerkki
@Observable
class ShoppingCart {
var items: [CartItem] = []
var totalPrice: Double = 0.0
func addItem(_ item: CartItem) {
items.append(item)
totalPrice += item.price
}
}
// Tilan seuraaminen Observations-sekvenssin avulla
let cart = ShoppingCart()
Task {
let priceUpdates = Observations { cart.totalPrice }
for await newTotal in priceUpdates {
print("Kokonaishinta päivittyi: \(newTotal) €")
}
}
Observations tarkkailee automaattisesti kaikkia ominaisuuksia, joihin viitataan sen sulkeumassa. Yllä seurataan vain totalPrice-ominaisuutta, joten sekvenssi tuottaa uuden arvon aina kun hinta muuttuu. Jos viittaisit useampaan ominaisuuteen, minkä tahansa niistä muuttuminen laukaisisi päivityksen.
Transaktionaaliset päivitykset
Yksi Observations-tyypin hienoimmista ominaisuuksista on transaktionaalisuus. Kun useita ominaisuuksia muutetaan synkronisesti, tarkkailijat näkevät vain lopputuloksen — eivät välitiloja. Transaktio päättyy seuraavaan await-kohtaan. Eli alla tarkkailija saa vain yhden päivityksen, vaikka kolme ominaisuutta muuttuu:
@Observable
class OrderManager {
var items: [OrderItem] = []
var totalPrice: Double = 0.0
var itemCount: Int = 0
func addItem(_ item: OrderItem) {
// Nämä kaikki muutokset ovat osa samaa transaktiota
items.append(item)
totalPrice += item.price
itemCount += 1
// Tarkkailija saa yhden päivityksen kaikkien muutosten jälkeen
}
}
Task {
let orderUpdates = Observations {
(orderManager.items, orderManager.totalPrice, orderManager.itemCount)
}
for await (items, total, count) in orderUpdates {
print("Tilaus päivittyi: \(count) tuotetta, yhteensä \(total) €")
}
}
Did-set-semantiikka
Observations-sekvenssit käyttävät did-set-semantiikkaa, eli arvot vastaanotetaan sen jälkeen kun ominaisuudet on muutettu. Tämä eroaa aiemmasta withObservationTracking-funktiosta, joka käytti will-set-semantiikkaa. Did-set on intuitiivisempi — vastaanotettu arvo on aina ajan tasalla.
Muistinhallinta ja weak self
Kun käytät Observations-sekvenssiä luokan sisällä, muista huolehtia muistinhallinnasta [weak self] -kaappauksella. Vahvat viittaussilmukat ovat edelleen mahdollisia (ja yleinen virhe):
@Observable
class DataManager {
var data: [String] = []
private var observationTask: Task?
func startObserving(_ source: DataSource) {
observationTask = Task { [weak self, weak source] in
guard let source else { return }
let updates = Observations { source.latestData }
for await newData in updates {
guard let self else { return }
self.data = newData
}
}
}
func stopObserving() {
observationTask?.cancel()
observationTask = nil
}
}
Huomaa miten sekä self että tarkkailtava objekti kaapataan heikolla viittauksella. guard let -tarkistus varmistaa, ettei sekvenssi jatku turhaan jos kohde on jo vapautettu muistista.
Käytännön migraatio-opas
Hyvä uutinen: olemassa olevan projektin migratointi voidaan tehdä asteittain. Ei tarvitse muuttaa kaikkea kerralla.
Vaihe 1: Päivitä työkaluketju
Varmista, että käytät Xcode 26:ta tai uudempaa. Swift 6.2:n ominaisuudet eivät ole saatavilla aiemmissa versioissa. Päivitä myös projektin Swift-kieliversio.
Vaihe 2: Ota käyttöön ominaisuudet asteittain
Suosittelen ottamaan ominaisuudet käyttöön tässä järjestyksessä:
- DefaultIsolation — ota käyttöön oletuseristys MainActorilla
- NonisolatedNonsendingByDefault — muuta asynkronisten funktioiden oletuskäyttäytyminen
- InferIsolatedConformances — anna kääntäjän päätellä eristetyt protokollayhteensopivuudet
- GlobalActorIsolatedTypesUsability — paranna globaaliaktorieristettyjen tyyppien käytettävyyttä
- InferSendableFromCaptures — päättele Sendable automaattisesti kaapatuista arvoista
Täydellinen Package.swift-konfiguraatio
Tässä koko konfiguraatio, joka ottaa käyttöön kaikki suositellut ominaisuudet:
// Package.swift
let package = Package(
name: "MySwiftApp",
platforms: [.iOS(.v26), .macOS(.v26)],
targets: [
.executableTarget(
name: "MySwiftApp",
swiftSettings: [
.defaultIsolation(MainActor.self),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("DisableOutwardActorInference"),
.enableUpcomingFeature("GlobalActorIsolatedTypesUsability"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("InferSendableFromCaptures")
]
)
]
)
Vinkkejä asteittaiseen käyttöönottoon
- Aloita yhdestä moduulista. Jos projektisi koostuu useista moduuleista, ota ominaisuudet käyttöön ensin yhdessä ja varmista että kaikki pelaa ennen laajentamista.
- Tarkista @concurrent-tarpeet. Kun otat käyttöön
NonisolatedNonsendingByDefault-ominaisuuden, käy läpi kaikkinonisolated async-funktiot. Jos jokin niistä on tarkoitettu taustasäikeelle, lisää@concurrent. - Testaa perusteellisesti. Erityisesti UI-päivitykset ja verkkokutsut on syytä testata huolellisesti, koska aktorikontekstin muutokset voivat vaikuttaa suoritusjärjestykseen.
- Hyödynnä kääntäjän varoituksia. Swift 6.2:n kääntäjä antaa selkeitä varoituksia ja korjausehdotuksia — seuraa niitä migraation aikana.
- Älä poista vanhoja @MainActor-merkintöjä heti. Ne eivät aiheuta haittaa, ja voit poistaa ne myöhemmin koodin siistimisvaiheessa.
Kokonainen esimerkki: Säätietosovelluksen ViewModel
Tarkastellaan realistista esimerkkiä, joka hyödyntää kaikkia Swift 6.2:n uusia rinnakkaisuusominaisuuksia. Rakennetaan yksinkertainen säätietosovellus.
Datamallit
struct Weather: Codable, Sendable {
let temperature: Double
let description: String
let humidity: Int
let windSpeed: Double
}
struct DayForecast: Codable, Sendable, Identifiable {
let id: UUID
let date: Date
let highTemperature: Double
let lowTemperature: Double
let description: String
let icon: String
}
Verkkoasiakasluokka
Verkkoasiakasluokka käyttää @concurrent-attribuuttia, koska verkkokutsut ja JSON-dekoodaus kannattaa suorittaa taustalla:
class NetworkClient {
@concurrent
nonisolated func fetchCurrentWeather(city: String) async throws -> Weather {
let url = URL(string: "https://api.weather.com/current?city=\(city)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(Weather.self, from: data)
}
@concurrent
nonisolated func fetchForecast(city: String) async throws -> [DayForecast] {
let url = URL(string: "https://api.weather.com/forecast?city=\(city)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([DayForecast].self, from: data)
}
}
ViewModel
ViewModel käyttää @Observable-makroa ja hyödyntää async let -sidontaa rinnakkaisten kutsujen tekemiseen:
@Observable
class WeatherViewModel {
var currentWeather: Weather?
var forecast: [DayForecast] = []
var isLoading = false
var errorMessage: String?
private let networkClient = NetworkClient()
func loadWeather(for city: String) async {
isLoading = true
errorMessage = nil
do {
async let weather = networkClient.fetchCurrentWeather(city: city)
async let days = networkClient.fetchForecast(city: city)
let (weatherResult, forecastResult) = try await (weather, days)
currentWeather = weatherResult
forecast = forecastResult
} catch {
errorMessage = "Säätietojen lataaminen epäonnistui: \(error.localizedDescription)"
}
isLoading = false
}
}
Muutama huomio tästä koodista. WeatherViewModel ei tarvitse eksplisiittistä @MainActor-merkintää — oletuseristys hoitaa sen. loadWeather-funktio voi turvallisesti muokata UI-tilaa, koska se suoritetaan MainActorilla. Ja async let -sidonta mahdollistaa kahden verkkokutsun suorittamisen rinnakkain, kumpikin omalla taustasäikeellään.
Tämä on juuri se malli, johon Swift 6.2 pyrkii: suurin osa koodista suoritetaan yksinkertaisesti pääsäikeessä ilman ylimääräisiä merkintöjä, ja vain oikeasti raskaat operaatiot merkitään taustalle. Säikeistyksestä ei tarvitse murehtia muualla kuin siellä, missä se on oikeasti relevanttia.
SwiftUI-näkymä
struct WeatherView: View {
@State private var viewModel = WeatherViewModel()
var body: some View {
NavigationStack {
VStack {
if viewModel.isLoading {
ProgressView("Ladataan säätietoja...")
} else if let weather = viewModel.currentWeather {
WeatherCard(weather: weather)
List(viewModel.forecast) { day in
ForecastRow(forecast: day)
}
} else if let error = viewModel.errorMessage {
ContentUnavailableView(
"Virhe",
systemImage: "exclamationmark.triangle",
description: Text(error)
)
}
}
.navigationTitle("Sää")
.task {
await viewModel.loadWeather(for: "Helsinki")
}
}
}
}
Tilan seuraaminen Observations-sekvenssin avulla
Jos haluat seurata tiettyä ominaisuutta ohjelmallisesti (vaikkapa analytiikkaa varten), Observations-sekvenssi on siihen juuri oikea työkalu:
// Esimerkki: Seurataan lämpötilan muutoksia analytiikkaa varten
class WeatherAnalytics {
private var trackingTask: Task?
func startTracking(_ viewModel: WeatherViewModel) {
trackingTask = Task { [weak viewModel] in
guard let viewModel else { return }
let temperatureChanges = Observations {
viewModel.currentWeather?.temperature
}
for await temperature in temperatureChanges {
if let temp = temperature {
print("Lämpötila päivittyi: \(temp)°C")
await AnalyticsService.shared.log(
event: "temperature_update",
value: temp
)
}
}
}
}
func stopTracking() {
trackingTask?.cancel()
trackingTask = nil
}
}
Nimetyt tehtävät ja parempi debuggaus
Swift 6.2 tuo SE-0469:n myötä mahdollisuuden nimetä Task-tehtäviä. Tämä on yllättävän suuri parannus käytännön kehitystyössä.
Tehtävien nimeäminen
Aiemmin tehtävät olivat nimettömiä, mikä teki niiden tunnistamisesta debuggerissa melko hankalaa. Nyt voit antaa jokaiselle tehtävälle selkeän nimen:
Task(name: "Säätietojen lataus") {
await viewModel.loadWeather(for: "Helsinki")
}
Task(name: "Kuvan käsittely - profiilikuva") {
await imageProcessor.processProfileImage(data)
}
// Tehtäväryhmissä jokainen alatehtävä voidaan nimetä
await withTaskGroup(of: UIImage.self) { group in
for (index, imageData) in imageDatas.enumerated() {
group.addTask(name: "Kuvan käsittely \(index + 1)/\(imageDatas.count)") {
try await processor.processImage(imageData)
}
}
}
Nimen lukeminen ohjelmallisesti
Tehtävän nimi on luettavissa myös koodista käsin — kätevää erityisesti lokituksessa:
func performWork() async {
if let taskName = Task.name {
print("Suoritetaan tehtävää: \(taskName)")
}
// ... varsinainen työ
}
LLDB-debuggerin parannukset
Swift 6.2 parantaa myös LLDB-debuggerin tukea asynkroniselle koodille merkittävästi:
- Luotettava asynkroninen askellus — voit nyt astua asynkronisten funktioiden sisään ilman että debuggeri "kadottaa" suorituksen kontekstin säikeen vaihdon yhteydessä.
- Tehtäväkontekstin näyttäminen — breakpointissa LLDB näyttää nyt millä tehtävällä koodi suoritetaan, pelkän säikenumeron sijaan.
- Parannettu kutsupinon näyttö — LLDB seuraa nyt tehtävän asynkronista kutsupinoa, joten näet koko kutsuketjun vaikka suoritus olisi siirtynyt eri säikeelle.
- Nimettyjen tehtävien tunnistaminen — nimetyt tehtävät näkyvät LLDB:ssä nimellään, mikä helpottaa monimutkaisten tehtävähierarkioiden ymmärtämistä.
Nämä parannukset yhdessä nimettyjen tehtävien kanssa tekevät rinnakkaisen koodin debuggauksesta vihdoin käytännöllistä. Se on ollut pitkään yksi suurimmista kipupisteistä.
Lisäominaisuudet
Pääominaisuuksien lisäksi Swift 6.2 sisältää muitakin tärkeitä parannuksia.
InferIsolatedConformances (SE-0470)
Tämä ratkaisee yleisen ongelman, jossa MainActor-eristetty tyyppi ei voinut automaattisesti täyttää protokollavaatimuksia samalla eristyksellä. Nyt kääntäjä päättelee tämän automaattisesti:
protocol DataProvider {
func fetchData() async -> [String]
}
// Swift 6.2 päättelee automaattisesti, että tämä
// yhteensopivuus on @MainActor-eristetty
class MyDataProvider: DataProvider {
func fetchData() async -> [String] {
return ["Helsinki", "Espoo", "Tampere"]
}
}
GlobalActorIsolatedTypesUsability (SE-0434)
Tekee globaaliaktorieristettyjen tyyppien käytöstä joustavampaa. Kääntäjä päättelee automaattisesti @Sendable-yhteensopivuuden globaaliaktorieristettyille funktioille ja sulkeumille. Lisäksi Sendable-tyyppisten tallennettujen ominaisuuksien käsittely muuttuu luontevammaksi moduulin sisällä.
InferSendableFromCaptures
Parantaa Swiftin kykyä päätellä @Sendable-yhteensopivuus automaattisesti metodeille ja avainpolku-literaaleille. Jos metodi kuuluu Sendable-tyypille, kääntäjä hoitaa merkinnät puolestasi.
DisableOutwardActorInference
Estää aktorieristyksen "leviämisen" tyypistä sen jäseniin odottamattomilla tavoilla. Aiemmin kääntäjä saattoi päätellä eristyksen kontekstista tavalla, joka ei ollut tarkoitettu. Nyt eristys on aina eksplisiittistä tai peräisin oletuseristyksestä.
Suorituskyky ja muistikäyttö
Lähestyttävä rinnakkaisuus ei paranna pelkästään kehittäjäkokemusta — siitä seuraa myös konkreettisia suorituskykyetuja.
Vähemmän turhia aktorinvaihtoja
Koska nonisolated(nonsending)-funktiot pysyvät nyt kutsujansa aktorilla, turhia aktorinvaihtoja tapahtuu huomattavasti vähemmän. Jokainen vaihto vaatii kontekstin tallentamisen ja palauttamisen, ja nyt ne turhat eliminoidaan automaattisesti.
Tehokkaammat Observations-päivitykset
Observations-tyypin transaktionaalisuus tarkoittaa, että käyttöliittymä päivitetään harvemmin mutta tehokkaammin. Synkroniset muutokset kootaan yhteen ja lähetetään yhtenä päivityksenä, mikä vähentää turhia UI-renderöintejä merkittävästi.
Tietoisempi rinnakkaisuus
Koska taustasäikeelle siirtyminen on nyt eksplisiittinen valinta @concurrent-attribuutin kautta, kehittäjät tekevät tietoisempia päätöksiä. Rinnakkaisuutta käytetään vain siellä missä se on oikeasti tarpeen, mikä johtaa tehokkaampaan resurssien käyttöön.
Parhaat käytännöt ja yhteenveto
Swift 6.2:n lähestyttävä rinnakkaisuus on iso askel eteenpäin. Se tekee rinnakkaisuudesta sen, mitä sen olisi aina pitänyt olla — työkalun jota käytetään tarvittaessa, ei estettä joka on ylitettävä joka projektissa.
Uudet projektit
- Ota käyttöön
defaultIsolation(MainActor.self)kaikissa uusissa projekteissa. Poistaa valtaosan turhista varoituksista. - Ota käyttöön
NonisolatedNonsendingByDefaultvarmistaaksesi intuitiivisen käyttäytymisen. - Käytä
@Observable-makroa jaObservations-sekvenssiä tilan hallintaan Combinen sijaan.
Olemassa olevat projektit
- Migratoikaa moduuli kerrallaan. Aloita uusimmista moduuleista tai niistä joissa on eniten varoituksia.
- Testaa jokaisen vaiheen jälkeen. Erityisesti
NonisolatedNonsendingByDefaultvoi muuttaa olemassa olevan koodin käyttäytymistä. - Merkitse taustatehtävät
@concurrent-attribuutilla. Käy läpi kaikkinonisolated async-funktiot ja varmista oikeat merkinnät.
Rinnakkaisuuden suunnittelu
- Käytä
@concurrentvain kun tarvitset taustasuoritusta. Älä merkitse funktioita varmuuden vuoksi — käytä sitä vain kun se on oikeasti tarpeen. - Hyödynnä
async let-sidontaa. Se on selkein tapa ilmaista rinnakkaisia operaatioita. - Suosi
Observations-sekvenssiä. Se on selkeämpi ja tehokkaampi kuin vanhawithObservationTracking. - Nimeä tehtävät.
Task(name:)helpottaa debuggausta valtavasti, erityisesti monimutkaisissa sovelluksissa.
Yhteenveto
Swift 6.2:n lähestyttävä rinnakkaisuus edustaa merkittävää filosofista muutosta. Sen sijaan että kaikki pakotetaan ymmärtämään koko rinnakkaisuusmalli kerralla, Swift tarjoaa nyt portaittaisen polun:
- Aloittelija kirjoittaa tavallista koodia, joka toimii turvallisesti MainActorilla.
- Keskitason kehittäjä käyttää
async/await-syntaksia luottaen siihen, että koodi pysyy oikealla aktorilla. - Kokenut kehittäjä optimoi suorituskykyä
@concurrent-attribuutilla ja eksplisiittisellä rinnakkaisuudella.
Tämä malli tekee Swiftistä vihdoin kielen, jossa rinnakkaisuus on oikeasti lähestyttävää — ei vain teoriassa. Jokainen kehittäjä voi valita oman tasonsa ilman turhia esteitä.
Swift 6.2 ja Xcode 26 ovat saatavilla nyt. Suosittelen aloittamaan uuden projektin näillä ominaisuuksilla tai kokeilemaan niitä pienessä olemassa olevassa moduulissa. Kun näet miten paljon puhtaampaa koodista tulee, et halua palata takaisin. Oma kokemukseni on, että suurin ero näkyy siinä miten paljon vähemmän aikaa menee kääntäjävaroitusten kanssa kamppailuun — ja miten paljon enemmän aikaa jää varsinaiseen kehittämiseen.