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.

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 Editorial Team

Our team of expert writers and editors.