Przystępna współbieżność w Swift 6.2 — kompletny przewodnik po nowym modelu concurrency

Kompletny przewodnik po Approachable Concurrency w Swift 6.2. Domyślna izolacja MainActor, atrybut @concurrent, Task.immediate, nazwane taski i praktyczne wskazówki migracji z kodem.

Swift 6.2 Concurrency 2026: Kompletny Guide

Wprowadzenie — dlaczego Swift 6.2 zmienia zasady gry

Pamiętacie moment, kiedy Apple zaprezentowało Swift 6.0? Programiści dostali całkiem potężne narzędzia do pisania bezpiecznego, współbieżnego kodu — aktorów, structured concurrency i ścisłe sprawdzanie izolacji danych. Brzmi świetnie na papierze. Problem w tym, że bariera wejścia okazała się absurdalnie wysoka.

Typowy projekt SwiftUI, który wcześniej kompilował się bez zająknięcia, po włączeniu trybu strict concurrency nagle wyrzucał dziesiątki ostrzeżeń i błędów. Pojawił się nawet termin async contamination — ta frustrująca konieczność oznaczania kolejnych funkcji jako async i @Sendable, mimo że kod w gruncie rzeczy działał na jednym wątku.

No i tu wchodzi Swift 6.2.

Wydany we wrześniu 2025 roku, odpowiada na tę frustrację filozofią Approachable Concurrency — współbieżności, która jest faktycznie łatwa w adopcji. Zamiast zmuszać programistów do zrozumienia wszystkiego na raz, Swift 6.2 pozwala na stopniowe wdrażanie równoległości: zacznij od sekwencyjnego kodu, dodaj async/await tam, gdzie to potrzebne, a aktorów i @concurrent wprowadzaj dopiero wtedy, gdy faktycznie chcesz przetwarzać dane równolegle.

W tym artykule omówimy wszystkie kluczowe nowości dotyczące współbieżności w Swift 6.2 — domyślną izolację na MainActor, atrybut @concurrent, zmienione zachowanie nonisolated async, nowe narzędzia jak Task.immediate i nazwane taski, a także praktyczne wskazówki migracji. Będzie konkretnie i z kodem.

Domyślna izolacja na MainActor (SE-0466)

Najważniejsza zmiana w Swift 6.2? Możliwość ustawienia domyślnej izolacji na MainActor dla całego modułu. W praktyce oznacza to, że każda klasa, struktura, metoda i właściwość w danym module jest automatycznie izolowana na głównym aktorze — chyba że jawnie zdecydujesz inaczej.

Do tej pory typowy kod SwiftUI wymagał ręcznych oznaczeń wszędzie:

// Swift 6.0 — wymagane jawne oznaczenia
@MainActor
class DataController {
    func load() { }
    func save() { }
}

@MainActor
struct ContentView: View {
    let controller = DataController()
    
    var body: some View {
        Button("Załaduj") {
            controller.load()
        }
    }
}

W Swift 6.2, po włączeniu flagi kompilatora -default-isolation MainActor, ten sam kod działa bez żadnych adnotacji:

// Swift 6.2 — z domyślną izolacją MainActor
class DataController {
    func load() { }
    func save() { }
}

struct ContentView: View {
    let controller = DataController()
    
    var body: some View {
        Button("Załaduj") {
            controller.load()
        }
    }
}

To podejście ma ogromne znaczenie szczególnie dla projektów, które składają się głównie z kodu UI. Bądźmy szczerzy — większość logiki w typowej aplikacji SwiftUI i tak musi działać na głównym wątku, więc domyślna izolacja na MainActor po prostu eliminuje potrzebę ręcznego oznaczania setek typów i metod. Mniej boilerplate'u, ten sam efekt.

Jak włączyć domyślną izolację?

Masz dwa sposoby:

Sposób 1: Flaga kompilatora

Dodaj flagę -default-isolation MainActor w ustawieniach budowania w Xcode (Build Settings → Other Swift Flags).

Sposób 2: W Swift Package Manager

// swift-tools-version: 6.2
import PackageDescription

let package = Package(
    name: "MojaAplikacja",
    targets: [
        .executableTarget(
            name: "MojaAplikacja",
            swiftSettings: [
                .defaultIsolation(MainActor.self)
            ]
        )
    ]
)

Kiedy nie stosować domyślnej izolacji MainActor?

To nie jest rozwiązanie na wszystko. Unikaj go w:

  • Bibliotekach i frameworkach, które mają być niezależne od kontekstu wykonania
  • Modułach odpowiedzialnych za ciężkie obliczenia w tle
  • Kodzie sieciowym i warstwach dostępu do danych, które powinny działać poza głównym wątkiem

Funkcje nonisolated async — zmiana kontekstu wykonania (SE-0461)

Jedna z najbardziej zaskakujących zmian w Swift 6.2 dotyczy sposobu, w jaki wykonują się funkcje oznaczone jako nonisolated async. Szczerze mówiąc, stare zachowanie było po prostu nieintuicyjne:

  • Funkcja nonisolated (synchroniczna) — wykonywała się na aktorze wywołującego (np. na MainActor, jeśli była wywołana z głównego wątku).
  • Funkcja nonisolated async — automatycznie przeskakiwała na globalny executor (wątek w tle), nawet jeśli nikt o to nie prosił.

To zachowanie powodowało sporo problemów. Programiści musieli oznaczać typy jako Sendable, ponieważ dane były przesyłane między aktorami, nawet gdy logicznie nie było to konieczne. Frustrujące.

Nowe domyślne zachowanie: nonisolated(nonsending)

W Swift 6.2, dzięki propozycji SE-0461, funkcje nonisolated async domyślnie zachowują się jak nonisolated(nonsending) — czyli nie zmieniają kontekstu wykonania. Wywołujesz z MainActor? Wykona się na MainActor. Z własnego aktora? Zostanie na tym aktorze. Proste i logiczne.

// Swift 6.2 — domyślne zachowanie
class NetworkService {
    // Ta funkcja wykona się na aktorze wywołującego
    // Nie przeskoczy na globalny executor
    func fetchData() async throws -> Data {
        let url = URL(string: "https://api.example.com/data")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
}

// Wywołanie z MainActor — fetchData() wykona się na MainActor
@MainActor
func loadContent() async {
    let service = NetworkService()
    do {
        let data = try await service.fetchData()
        // Aktualizacja UI odbywa się na tym samym wątku
        updateUI(with: data)
    } catch {
        showError(error)
    }
}

Aby włączyć to zachowanie jako upcoming feature, dodaj flagę:

.enableUpcomingFeature("NonisolatedNonsendingByDefault")

Korzyści z nowego modelu

Krótko i na temat:

  • Mniej wymogów Sendable — skoro dane nie przekraczają granic aktorów, nie musisz oznaczać typów jako Sendable.
  • Mniej niespodzianek — kod wykonuje się tam, skąd został wywołany. Koniec z tajemniczymi przeskokami na inne wątki.
  • Prostsza migracja — istniejący kod nie zmienia nagle wątku wykonania.

Atrybut @concurrent — jawne przeniesienie na wątek w tle

Skoro domyślnie funkcje nonisolated async wykonują się na aktorze wywołującego, pojawia się naturalne pytanie: jak jawnie przenieść obliczenia na wątek w tle? Odpowiedzią jest nowy atrybut @concurrent.

class ImageProcessor {
    // Ta funkcja ZAWSZE wykona się na globalnym executorze
    // (w wątku w tle), niezależnie od tego, kto ją wywołuje
    @concurrent
    func processImage(_ image: UIImage) async -> UIImage {
        // Ciężkie przetwarzanie obrazu w tle
        let filtered = applyFilters(to: image)
        let resized = resize(filtered, to: CGSize(width: 800, height: 600))
        return resized
    }
}

@MainActor
func handleImageSelection(_ image: UIImage) async {
    let processor = ImageProcessor()
    
    // processImage() przenosi się na wątek w tle
    let result = await processor.processImage(image)
    
    // Po zakończeniu wracamy na MainActor
    displayImage(result)
}

Moim zdaniem, @concurrent to jedna z najlepiej zaprojektowanych zmian w tym wydaniu. Intencja programisty jest od razu jasna: ta funkcja ma działać równolegle. To realizacja zasady progressive disclosure — współbieżność pojawia się tylko wtedy, gdy jawnie o nią poprosisz.

Kiedy używać @concurrent?

  • Ciężkie obliczenia — przetwarzanie obrazów, parsowanie dużych plików JSON
  • Operacje I/O, które nie powinny blokować głównego wątku
  • Batch processing danych
  • Algorytmy kryptograficzne i kompresja

Kiedy NIE używać @concurrent?

  • Proste wywołania sieciowe obsługiwane przez URLSession (same w sobie są asynchroniczne i radzą sobie bez tego)
  • Funkcje, które aktualizują UI
  • Kod, który operuje na izolowanych danych aktora

Task.immediate — natychmiastowe uruchomienie (SE-0472)

W Swift 6.0, kiedy tworzysz Task { ... }, closure nie jest wykonywane od razu — jest kolejkowane i uruchamiane w przyszłym cyklu pętli zdarzeń. W wielu przypadkach to zachowanie jest po prostu niepotrzebne i powoduje zauważalne opóźnienia.

Swift 6.2 wprowadza Task.immediate, który uruchamia closure natychmiast, jeśli jesteśmy już na odpowiednim executorze:

@MainActor
func setupView() {
    // Task.immediate — jeśli już jesteśmy na MainActor,
    // closure wykona się natychmiast, bez czekania na następny cykl
    Task.immediate {
        let data = await fetchInitialData()
        updateUI(with: data)
    }
    
    // Tradycyjny Task — zawsze jest kolejkowany
    Task {
        let data = await fetchInitialData()
        updateUI(with: data)
    }
}

Jeśli pracowaliście ze SwiftUI, pewnie znacie to irytujące migotanie interfejsu przy inicjalizacji widoków. Task.immediate pozwala tego uniknąć — dane zaczynają się ładować od razu, bez czekania na następny cykl pętli zdarzeń. Mała zmiana, duży wpływ na UX.

Nazwane taski — lepsza diagnostyka (SE-0469)

Debugowanie współbieżnego kodu to (delikatnie mówiąc) wyzwanie. Swift 6.2 wprowadza możliwość nadawania nazw taskom, co znacznie ułatwia życie przy identyfikacji i śledzeniu w narzędziach diagnostycznych:

// Nadanie nazwy taskowi
let downloadTask = Task(name: "PobieranieProfilu") {
    let profile = try await fetchUserProfile()
    return profile
}

// Odczytanie nazwy bieżącego taska
Task(name: "PrzetwarzanieDanych") {
    print("Aktualny task: \(Task.name ?? "Nieznany")")
    
    // Nazwy pojawiają się w debuggerze LLDB
    // oraz w Instruments
    await processData()
}

Nazwane taski integrują się z narzędziami Apple, co jest naprawdę miłym dodatkiem:

  • LLDB — nazwa taska jest widoczna przy breakpointach w asynchronicznym kodzie
  • Instruments — łatwiejsze filtrowanie i identyfikacja tasków
  • Xcode Thread Navigator — nazwy tasków pojawiają się w liście wątków

Izolowane deinicjalizatory (SE-0371)

Przed Swift 6.2 deinicjalizatory (deinit) nie mogły bezpiecznie uzyskiwać dostępu do izolowanych właściwości aktora. Klasyczny problem: masz klasę oznaczoną jako @MainActor, chcesz posprzątać zasoby w deinit, a kompilator mówi „nie".

Swift 6.2 rozwiązuje ten problem, pozwalając na oznaczenie deinicjalizatora jako isolated:

@MainActor
class MediaPlayer {
    var currentTrack: Track?
    var audioEngine: AudioEngine
    
    init() {
        audioEngine = AudioEngine()
    }
    
    // Izolowany deinit — bezpieczny dostęp do właściwości
    // izolowanych na MainActor
    isolated deinit {
        audioEngine.stop()
        currentTrack = nil
        NotificationCenter.default.post(
            name: .playerDeallocated,
            object: nil
        )
    }
}

Słowo kluczowe isolated gwarantuje, że deinicjalizator wykona się na tym samym aktorze, na którym jest izolowany typ. Koniec z potencjalnymi wyścigami danych podczas niszczenia obiektów.

Eskalacja priorytetów tasków (SE-0462)

System operacyjny może dynamicznie zmieniać priorytety tasków — na przykład gdy użytkownik aktywnie czeka na wynik operacji, system podnosi jej priorytet. Swift 6.2 pozwala teraz na wykrywanie i reagowanie na takie zmiany, co jest całkiem sprytne:

func performHeavyComputation() async throws -> Result {
    try await withTaskPriorityEscalationHandler {
        // Główna logika obliczeniowa
        var result = Result()
        for chunk in dataChunks {
            try Task.checkCancellation()
            result.append(process(chunk))
        }
        return result
    } onPriorityEscalated: { oldPriority, newPriority in
        print("Priorytet zmieniony z \(oldPriority) na \(newPriority)")
        // Możesz np. zwiększyć rozmiar batcha
        // lub przenieść obliczenia na wydajniejszy rdzeń
    }
}

To narzędzie jest szczególnie przydatne w aplikacjach wykonujących długotrwałe operacje — pozwala dynamicznie dostosowywać strategię przetwarzania do priorytetów systemowych.

Obserwacja transakcyjna — Observations (SE-0475)

Swift 6.2 wprowadza nowy typ Observations, który pozwala na strumieniowe obserwowanie zmian w obiektach @Observable. Jeśli kiedykolwiek marzyliście o prostszej alternatywie dla Combine do reaktywnego reagowania na zmiany — oto ona:

@Observable
class GameState {
    var score = 0
    var level = 1
    var lives = 3
}

let game = GameState()

// Tworzenie strumienia obserwacji
let scoreUpdates = Observations {
    game.score
}

// Reagowanie na zmiany w pętli async
Task {
    for await currentScore in scoreUpdates {
        print("Aktualny wynik: \(currentScore)")
        
        if currentScore >= 1000 {
            await showAchievement("Zdobyto 1000 punktów!")
        }
    }
}

Typ Observations działa jak AsyncSequence — emituje nowe wartości za każdym razem, gdy obserwowana właściwość się zmieni. Proste, czytelne i bez konieczności importowania Combine.

InlineArray — tablice o stałym rozmiarze (SE-0453)

Ok, to nie jest bezpośrednio powiązane z współbieżnością, ale nie mogę tego pominąć. InlineArray to jedna z najciekawszych nowości Swift 6.2 z perspektywy wydajności — tablica o stałym rozmiarze, której elementy są przechowywane inline (na stosie), bez alokacji na stercie:

// Deklaracja InlineArray
var kolory: InlineArray<4, String> = ["Czerwony", "Zielony", "Niebieski", "Żółty"]

// Typ może być inferowany
var liczby: InlineArray = [1, 2, 3, 4, 5]

// Dostęp przez indeks
kolory[0] = "Pomarańczowy"

// Iteracja
for i in kolory.indices {
    print(kolory[i])
}

// Idealne do małych, stałych kolekcji
struct Vertex {
    var position: InlineArray<3, Float>  // x, y, z
    var color: InlineArray<4, Float>     // r, g, b, a
}

Benchmarki pokazują 20-30% lepszą wydajność w porównaniu do standardowego Array w przypadkach, gdy rozmiar kolekcji jest znany w czasie kompilacji. Szczególnie przydatne w:

  • Grafice 3D i przetwarzaniu wektorów
  • Buforach o stałym rozmiarze
  • Protokołach komunikacyjnych z pakietami o ustalonej strukturze
  • Embedded Swift i programowaniu niskopoziomowym

Dodatkowe usprawnienia językowe

Interpolacja stringów z wartościami domyślnymi (SE-0477)

Drobna zmiana, ale znacząco poprawia czytelność. Swift 6.2 pozwala na podanie wartości domyślnej dla opcjonalnych wartości w interpolacji stringów:

var imie: String? = nil
var wiek: Int? = 28

print("Witaj, \(imie, default: "Gościu")!")  // "Witaj, Gościu!"
print("Wiek: \(wiek, default: "nieznany")")    // "Wiek: 28"

Koniec z niezręcznym operatorem ?? wewnątrz interpolacji. Nareszcie.

weak let — niemutowalne słabe referencje (SE-0481)

final class Session: Sendable {
    weak let user: User?
    
    init(user: User?) {
        self.user = user
    }
}

// user może zostać zwolniony, ale referencja
// nie może być zmieniona na inny obiekt

weak let jest szczególnie przydatne w kontekście Sendable — niemutowalna słaba referencja jest bezpieczna do współdzielenia między wątkami, co rozwiązuje kolejny częsty ból głowy przy pracy z concurrency.

Raw identifiers — identyfikatory ze spacjami (SE-0451)

enum HTTPStatus: Int {
    case `200 OK` = 200
    case `404 Not Found` = 404
    case `500 Internal Server Error` = 500
}

// Użycie w testach — czytelniejsze nazwy
func `test pobieranie profilu użytkownika`() async throws {
    let profile = try await api.fetchProfile(id: 1)
    XCTAssertNotNil(profile)
}

Przyznam, że nie jestem w 100% przekonany do tego feature'a w produkcyjnym kodzie, ale w testach? Nazwy metod testowych stają się wreszcie czytelne bez konwencji camelCase.

Praktyczny przewodnik migracji do Swift 6.2

Dobra, przechodzimy do konkretów. Migracja do nowego modelu współbieżności wymaga przemyślanego podejścia — nie wrzucaj wszystkiego na raz. Oto sprawdzony plan działania.

Krok 1: Audyt istniejącego kodu

Zanim włączysz jakiekolwiek nowe funkcje, przejrzyj swój projekt:

  • Które moduły składają się głównie z kodu UI? → Kandydaci do domyślnej izolacji MainActor
  • Które moduły wykonują ciężkie obliczenia w tle? → Powinny używać @concurrent
  • Gdzie występują jawne adnotacje @MainActor? → Mogą zostać usunięte po włączeniu domyślnej izolacji

Krok 2: Stopniowe włączanie feature flags

Kluczowa rada — nie włączaj wszystkich zmian naraz. Swift 6.2 pozwala na stopniowe wdrażanie:

// Package.swift — stopniowe wdrażanie
.target(
    name: "MojaAplikacja",
    swiftSettings: [
        // Krok 1: Zacznij od tego
        .enableUpcomingFeature("InferSendableFromCaptures"),
        
        // Krok 2: Następnie dodaj
        .enableUpcomingFeature("GlobalActorIsolatedTypesUsability"),
        
        // Krok 3: Na końcu
        .enableUpcomingFeature("NonisolatedNonsendingByDefault"),
        .enableUpcomingFeature("DisableOutwardActorInference"),
        .enableUpcomingFeature("InferIsolatedConformances"),
    ]
)

Krok 3: Zidentyfikuj funkcje wymagające @concurrent

Po włączeniu NonisolatedNonsendingByDefault, przejrzyj swoje asynchroniczne funkcje. Te, które wcześniej automatycznie wykonywały się w tle, teraz będą działać na aktorze wywołującego. Dodaj @concurrent do tych, które naprawdę muszą działać równolegle:

// PRZED migracją — funkcja automatycznie szła na globalny executor
class DataParser {
    func parseCSV(_ data: Data) async -> [Record] {
        // ciężkie parsowanie...
    }
}

// PO migracji — jawnie oznaczamy jako @concurrent
class DataParser {
    @concurrent
    func parseCSV(_ data: Data) async -> [Record] {
        // ciężkie parsowanie nadal w tle
    }
}

Krok 4: Testowanie

Swift 6.2 wprowadza również nowe możliwości testowania — Exit Tests (ST-0008) do testowania krytycznych awarii oraz Attachments (ST-0009) do dołączania danych diagnostycznych do raportów z testów:

import Testing

@Test func walidacjaDanychWejściowych() async throws {
    let validator = InputValidator()
    
    // Test normalnego przypadku
    let result = validator.validate("poprawne dane")
    #expect(result == .valid)
    
    // Test z timeoutem
    await confirmation("Operacja zakończona na czas") { confirm in
        Task.immediate {
            let data = try? await fetchWithTimeout()
            if data != nil {
                confirm()
            }
        }
    }
}

@Test func nieprawidłoweWejściePowodujeAwarię() async throws {
    await #expect(processExitsWith: .failure) {
        let validator = InputValidator()
        validator.validate(nil) // fatalError
    }
}

Wzorce projektowe dla Swift 6.2

Nowy model współbieżności w Swift 6.2 zachęca do stosowania określonych wzorców projektowych. Oto kilka rekomendowanych podejść, które sprawdzają się w praktyce.

Wzorzec: Warstwa usług z jawną współbieżnością

Ten wzorzec dzieli odpowiedzialności: warstwa sieciowa działa w tle (dzięki @concurrent), a ViewModel i widoki pozostają na MainActor.

// Warstwa sieciowa — jawnie współbieżna
class APIClient {
    @concurrent
    func fetchUsers() async throws -> [User] {
        let url = URL(string: "https://api.example.com/users")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([User].self, from: data)
    }
    
    @concurrent
    func fetchPosts(for userId: Int) async throws -> [Post] {
        let url = URL(string: "https://api.example.com/users/\(userId)/posts")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([Post].self, from: data)
    }
}

// ViewModel — domyślnie na MainActor
@Observable
class UsersViewModel {
    var users: [User] = []
    var isLoading = false
    var error: Error?
    
    private let api = APIClient()
    
    func loadUsers() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            // fetchUsers() wykonuje się w tle (@concurrent)
            // po zakończeniu wracamy na MainActor
            users = try await api.fetchUsers()
        } catch {
            self.error = error
        }
    }
}

// Widok SwiftUI — na MainActor
struct UsersView: View {
    @State private var viewModel = UsersViewModel()
    
    var body: some View {
        List(viewModel.users, id: \.id) { user in
            Text(user.name)
        }
        .overlay {
            if viewModel.isLoading {
                ProgressView()
            }
        }
        .task {
            await viewModel.loadUsers()
        }
    }
}

Wzorzec: Równoległe pobieranie danych

Kiedy musisz pobrać wiele zasobów jednocześnie, async let w połączeniu z @concurrent daje bardzo czysty kod:

@Observable
class DashboardViewModel {
    var stats: DashboardStats?
    var isLoading = false
    
    private let api = APIClient()
    
    func loadDashboard() async {
        isLoading = true
        defer { isLoading = false }
        
        // Równoległe pobieranie wielu zasobów
        async let users = api.fetchUsers()
        async let posts = api.fetchPosts(for: 1)
        async let notifications = api.fetchNotifications()
        
        do {
            let (u, p, n) = try await (users, posts, notifications)
            stats = DashboardStats(
                userCount: u.count,
                postCount: p.count,
                notificationCount: n.count
            )
        } catch {
            print("Błąd ładowania dashboardu: \(error)")
        }
    }
}

Porównanie: Swift 6.0 vs Swift 6.2

Żeby mieć pełen obraz — oto kluczowe różnice w zachowaniu kodu współbieżnego między obiema wersjami:

  • Domyślna izolacja — Swift 6.0: brak (wymaga jawnych adnotacji); Swift 6.2: możliwość ustawienia MainActor jako domyślnej
  • nonisolated async — Swift 6.0: wykonuje się na globalnym executorze; Swift 6.2: wykonuje się na aktorze wywołującego
  • Przeniesienie do tła — Swift 6.0: automatyczne dla nonisolated async; Swift 6.2: wymaga jawnego @concurrent
  • Sendable — Swift 6.0: wymagane częściej; Swift 6.2: wymagane rzadziej dzięki zachowaniu kontekstu wykonania
  • Tworzenie tasków — Swift 6.0: tylko kolejkowane; Swift 6.2: Task.immediate dla natychmiastowego uruchomienia
  • Debugowanie — Swift 6.0: ograniczone; Swift 6.2: nazwane taski, lepsza integracja z LLDB
  • Deinicjalizatory — Swift 6.0: nie mogą uzyskać dostępu do izolowanych danych; Swift 6.2: isolated deinit

Wskazówki dotyczące wydajności

Na koniec kilka praktycznych porad, które warto mieć z tyłu głowy:

  1. Nie nadużywaj @concurrent — Każde użycie powoduje przeskoczenie na globalny executor. Jeśli operacja jest szybka, koszt przełączenia kontekstu może przewyższyć korzyści. Mierz, nie zgaduj.
  2. Używaj InlineArray dla małych kolekcji — Alokacja na stosie jest znacznie szybsza niż na stercie. Dla kolekcji o znanym rozmiarze (współrzędne 3D, kolory RGBA) to oczywisty wybór.
  3. Preferuj Task.immediate — Jeśli jesteś już na odpowiednim executorze, Task.immediate eliminuje niepotrzebne opóźnienie kolejkowania.
  4. Profiluj z Instruments — Nazwane taski ułatwiają identyfikację wąskich gardeł. Korzystaj z tego.

Podsumowanie

Swift 6.2 to moim zdaniem najważniejsze wydanie od czasu wprowadzenia async/await. Zamiast wymuszać pełne zrozumienie modelu aktorów od pierwszego dnia, pozwala na stopniowe wdrażanie współbieżności. Kluczowe zmiany to:

  • Domyślna izolacja na MainActor — eliminuje ręczne oznaczanie setek typów
  • nonisolated(nonsending) jako domyślne zachowanie — funkcje async nie przeskakują na globalny executor bez jawnego pozwolenia
  • @concurrent — jasna intencja równoległego wykonania
  • Task.immediate i nazwane taski — lepsza kontrola i diagnostyka
  • Izolowane deinicjalizatory — bezpieczne czyszczenie zasobów

Filozofia progressive disclosure oznacza, że Swift wymaga od Ciebie tylko tyle wiedzy o współbieżności, ile faktycznie potrzebujesz. Pisz sekwencyjny kod, dodawaj async/await tam, gdzie to konieczne, a @concurrent tylko wtedy, gdy naprawdę chcesz równoległego wykonania. I szczerze? To jest dokładnie taki kierunek, w jakim język powinien zmierzać.

O Autorze Priya Raghavan

Priya spent six years at Instacart building the iOS shopper app, where she led the migration from UIKit to SwiftUI across 80+ screens and cut crash-free sessions from 99.2% to 99.87%. Before that, she was a contractor at a Bay Area design studio shipping App Store apps for two Fortune 500 retail clients. She focuses on practical SwiftUI architecture - what holds up when you have 12 engineers committing to the same codebase, not just toy MVVM examples. Her recent work involves The Composable Architecture, Swift concurrency migration audits, and reducing main-thread hangs on older devices like the iPhone XR that enterprise fleets still ship. Priya runs a small consultancy in Oakland and occasionally speaks at try! Swift NYC. She has been writing Swift since the Objective-C bridging days of 2015.