← Wszystkie wpisy

Swift 6.2 — współbieżność w praktyce: domyślnie MainActor, wychodź z premedytacją

Swift 6.0 uczynił wyścigi danych błędem kompilacji i przez rok kazał wszystkim za to płacić. Mechanizm sprawdzania ścisłej współbieżności (strict-concurrency) zamieniał zwykły kod UI w ścianę naruszeń Sendable oraz błędów „main actor-isolated property can not be referenced from a nonisolated context”. Diagnoza była trafna (ten kod faktycznie mógł wpaść w wyścig), ale ogrom komunikatów zagrzebał sygnał, więc wiele zespołów albo zostało przy trybie Swift 5, albo dosypywało @MainActor, aż błędy ucichły.

Swift 6.2 zmienia nie reguły, lecz wartość domyślną. Gwarancja bezpieczeństwa przed wyścigami danych pozostaje ta sama; zmieniło się to, od czego kompilator zaczyna. Po przyjęciu nowych wartości domyślnych większość tej ściany znika, ponieważ kompilator zakłada teraz to, co w aplikacji i tak już było prawdą: większość kodu działa na głównym aktorze (main actor), a wychodzi się z niego z premedytacją, w nazwanych miejscach. Taki model wdrażam w aplikacjach z rodziny 9411 i jest on jednym z elementów szerszego łuku ewolucji, który przegląd nowości w Swift na rok 2026 prześledza aż po propozycje 6.3 i 6.4, które po nim nastąpiły. Oto jak działa oraz sześć błędów, które wciąż dają się we znaki po jego włączeniu.

W skrócie

  • SE-0466 pozwala ustawić cały moduł domyślnie na @MainActor. Wystarczy ustawić SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor i przestać oznaczać adnotacjami każdy widok, model i view-model2.
  • SE-0461 sprawia, że funkcje nonisolated async działają domyślnie na aktorze wywołującego (nonisolated(nonsending)), więc wywołanie kodu asynchronicznego nie wymusza już przeskoku między aktorami ani towarzyszących mu błędów Sendable na granicy izolacji3.
  • @concurrent to furtka awaryjna. Oznaczenie funkcji jako @concurrent przenosi ciężką pracę CPU (dekodowanie, przetwarzanie obrazów) na wątek w tle — celowo i w sposób widoczny4.
  • Xcode 26 włącza obie te wartości domyślne dla nowych projektów. Zbiorczy przełącznik to SWIFT_APPROACHABLE_CONCURRENCY = YES5.
  • Model odwraca ciężar dowodu: zamiast dowodzić, że każda linia jest bezpieczna do uruchomienia poza głównym wątkiem, trzymasz wszystko na głównym wątku i dowodzisz tych nielicznych miejsc, w których go opuszczasz.
  • Po przełączeniu zostaje sześć konkretnych błędów. Wszystkie sześć mają jednoliniowe poprawki, gdy już dostrzeże się wzorzec.

Odwrócenie i dlaczego ma znaczenie

Stary model (Swift 6.0): kod jest nonisolated, dopóki się go nie odizoluje. Każdy typ dotykający stanu UI wymagał @MainActor, każde wywołanie asynchroniczne przez granicę izolacji wymagało zgodności z Sendable, a kompilator sygnalizował każdą lukę. W aplikacji, w której 95 procent kodu i tak już działa na głównym wątku, czas schodził na adnotowanie tych 95 procent, by opisać fakt, który nigdy nie budził wątpliwości.

Nowy model (Swift 6.2): kod jest na głównym aktorze, dopóki się go nie opuści. SE-0466 pozwala zadeklarować izolację głównego aktora jako domyślną dla modułu, więc widok, jego model i jego funkcje pomocnicze są wszystkie @MainActor bez ani jednej adnotacji2. SE-0461 znosi następnie drugi podatek: funkcja nonisolated async działa teraz na tym aktorze, który ją wywołał, zamiast przeskakiwać do globalnego egzekutora, więc oczekiwanie na nią nie przeciąga cię przez granicę izolacji ani nie wymaga Sendable na wszystkim w zasięgu3.

Model myślowy odpowiada temu, jak aplikacje UI naprawdę się zachowują. Domyślny główny wątek nie jest kompromisem; jest prawdą o aplikacji, której stanem są jej widoki. Współbieżność staje się wyjątkiem, po który się sięga — nazwanym i ograniczonym — a nie wszechobecnym warunkiem, przed którym broni się w każdej linii. Zadanie kompilatora przestawia się z „udowodnij, że to jest bezpieczne do współbieżnego uruchomienia” na „powiedziałeś, że to działa współbieżnie, więc udowodnij, że jest bezpieczne”, a to drugie pytanie pada w znacznie mniejszej liczbie miejsc.

Zespół rdzeniowy Swifta powiedział to wprost. Na WWDC 2026 Swift Group Lab Holly opisała, jak — projektując model współbieżności dzisiaj — funkcje nonisolated async od pierwszego dnia działałyby w kontekście wywołującego; sparafrazując, długo trzymała się modelu globalnej puli współbieżnej i do innego zdania przekonały ją problemy w rzeczywistych projektach. Doug dodał, że stara wartość domyślna pchała zbyt wiele typów ku Sendable. (Obie wypowiedzi są sparafrazowane z lokalnie spisanego nagrania sesji; Apple nie opublikowało napisów do tej sesji.)9

Włączanie

Dwa ustawienia kompilacji, oba w wartościach domyślnych Xcode 26 dla nowych projektów i oba warte jawnego ustawienia w istniejącym projekcie5:

SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor
SWIFT_APPROACHABLE_CONCURRENCY = YES

Pierwsze to SE-0466: moduł domyślnie przyjmuje izolację głównego aktora. Drugie to zbiorczy przełącznik włączający zestaw funkcji przystępnej współbieżności (approachable-concurrency), w tym zachowanie „wywołujący uruchamia” z SE-0461. W pakiecie Swift te same wartości domyślne ustawia się poprzez swiftSettings z odpowiadającymi flagami nadchodzących funkcji, a nie przez ustawienie kompilacji Xcode6.

Włącz oba w istniejącym projekcie, a liczba błędów gwałtownie spadnie, ponieważ większość tego, co sygnalizował mechanizm sprawdzający, była kodem głównego wątku, którego wcześniej nie mógł założyć za działający na głównym wątku. Pozostaje krótka lista autentycznych przypadków granicznych. Warto znać je z nazwy, bo każdy z nich to miejsce, w którym kod naprawdę opuszcza głównego aktora, a poprawka polega na precyzyjnym tego zadeklarowaniu.

Sześć błędów, które przetrwają przełączenie

Oto błędy ścisłej współbieżności, które wciąż pojawiają się przy SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, zebrane z migracji aplikacji rodziny 941 do tego modelu1. Każdy z nich to rzeczywista granica, a nie fałszywy alarm — dlatego poprawką jest precyzyjna adnotacja, a nie wyciszenie.

1. Funkcje czyste na typach Sendable. Czysta metoda na wyliczeniu Sendable (konstruktor URL, formatter) dziedziczy izolację głównego aktora przy domyślnym ustawieniu modułu, a następnie zgłasza błąd przy wywołaniu z kontekstu nonisolated: „Call to main actor-isolated instance method in a synchronous nonisolated context.” Metoda nie dotyka żadnego stanu, więc izolowanie jej do głównego aktora jest błędem. Należy oznaczyć ją jako nonisolated7:

nonisolated func searchURL(for query: String) -> URL? { ... }

2. Statyczne singletony w parametrach domyślnych. static let shared = Foo() jest przy domyślnym ustawieniu izolowany do głównego aktora, ale wartości parametrów domyślnych są ewaluowane w kontekście wywołującego, który często jest nonisolated: „Main actor-isolated static property ‘shared’ can not be referenced from a nonisolated context.” Należy uczynić tę statyczną właściwość nonisolated. Jeśli typ jest Sendable (lub @unchecked Sendable, ponieważ sam strzeżesz jego stanu), nie potrzeba żadnego niebezpiecznego kwalifikatora:

final class KeychainProxySecretStore: @unchecked Sendable {
    nonisolated static let shared = KeychainProxySecretStore()
}

3. Niezmienne stałe prymitywne. Ten sam problem z parametrem domyślnym dotyka zwykłą stałą: static let defaultInterval odwoływaną z nonisolated argumentu domyślnego. Poprawka jest identyczna, a stała jest trywialnie bezpieczna do współdzielenia:

nonisolated static let defaultInterval: TimeInterval = 15 * 60

4. Ciało Task odczytujące przechwycone self. Zewnętrzne domknięcie przechwytuje [weak self]; w środku Task { @MainActor in self?.foo() } odczytuje ten przechwycony opcjonał: „Reference to captured var ‘self’ in concurrently-executing code.” Task odczytuje powiązanie var z obejmującego zasięgu współbieżnie — i to jest wyścig. Należy ponownie przechwycić self na granicy Tasku, aby Task posiadał niezmienne powiązanie:

NotificationCenter.default.addObserver(...) { [weak self] _ in
    Task { @MainActor [weak self] in
        self?.value = next
    }
}

5. Wywołania zwrotne KVO odczytujące stan głównego aktora. Wywołanie zwrotne webView.observe(\.canGoBack) { wv, _ in ... } jest @Sendable, a więc nonisolated, lecz WKWebView.canGoBack jest izolowane do głównego aktora: „Main actor-isolated property ‘canGoBack’ can not be referenced from a Sendable closure.” KVO dostarcza synchronicznie na wątku, który zmutował wartość, a stan nawigacji WKWebView zmienia się wyłącznie na głównym wątku, więc odczyt jest poprawny. Należy to potwierdzić przez MainActor.assumeIsolated, co całkowicie usuwa przeskok Task i pozostaje synchroniczne8:

let pushNav: @Sendable (WKWebView?) -> Void = { [weak self] webView in
    MainActor.assumeIsolated {
        guard let self else { return }
        // safe to read webView?.canGoBack synchronously
    }
}

assumeIsolated to obietnica złożona kompilatorowi, a nie pytanie. Należy używać go tylko tam, gdzie niezmiennik czasu wykonania faktycznie zachodzi (udokumentowane wywołanie zwrotne na głównym wątku), ponieważ błędna obietnica to awaria, a nie ostrzeżenie.

6. Ciężka praca, która nie powinna być na głównym wątku. Tego mechanizm sprawdzający nie zasygnalizuje, a wyłapanie tego samemu jest najważniejsze. Przy domyślnym głównym aktorze synchroniczna metoda obciążająca CPU (dekodowanie z JSON dużego ładunku, zmiana rozmiaru obrazu) działa na głównym aktorze i powoduje zacinanie UI. Wartość domyślna trzyma cię na głównym wątku; @concurrent to sposób, by opuścić go z premedytacją4:

@concurrent
func decodeLargePayload(_ data: Data) async throws -> Report {
    try JSONDecoder().decode(Report.self, from: data)
}

@concurrent przenosi funkcję do globalnego egzekutora i jest z założenia wzajemnie wykluczające się z @MainActor, niestandardowym aktorem globalnym oraz nonisolated(nonsending): funkcja albo działa tam, gdzie jej wywołujący, albo celowo od niego ucieka — nigdy niejednoznacznie4. Dyscyplina, o jaką prosi nowy model, mieści się w całości w tym wzorcu. Zostań na głównym wątku ze wszystkim, co dotyka UI, i sięgaj po @concurrent tylko dla tej pracy, która w mierzalny sposób potrzebuje wątku w tle.

Kiedy wartości domyślne są dla ciebie niewłaściwe

Domyślny główny aktor pasuje do aplikacji: kod SwiftUI i UIKit jest w przeważającej mierze na głównym wątku, a wartość domyślna odpowiada rzeczywistości. Gorzej pasuje w dwóch przypadkach, a udawanie, że jest inaczej, marnuje czas.

  • Cel typu biblioteka lub framework bez UI. Warstwa sieciowa, parser czy silnik danych nie ma powodu, by domyślnie używać głównego aktora — a robienie tego wymusza @concurrent lub nonisolated na niemal wszystkim. Dla takich celów należy pozostawić SWIFT_DEFAULT_ACTOR_ISOLATION nieustawione i izolować z premedytacją, po staremu.
  • System silnie oparty na aktorach i współbieżności. Jeśli projekt naprawdę uruchamia wiele rzeczy równolegle (rzeczywisty potok, a nie aplikacja z kilkoma zadaniami w tle), domyślny główny aktor będzie z tobą walczył. Chcesz wtedy jawnych aktorów i kodu nonisolated, a domyślna wartość SE-0466 jest złym punktem wyjścia.

W przypadku aplikacji decyzja jest jednak prosta: włączyć oba ustawienia, pozwolić liczbie błędów się załamać i potraktować tę garstkę, która zostanie, jako mapę dokładnie tych miejsc, w których kod opuszcza główny wątek. Taka mapa jest warta posiadania. Stary model dawał tysiąc ostrzeżeń i żadnej mapy; nowy daje sześć uczciwych granic i wartość domyślną, która wreszcie odpowiada temu, jak aplikacja działa.

Ostatnia uwaga o szumie kontra sygnale. SourceKit pokaże w edytorze międzyplikowe błędy indeksu („Cannot find type X in scope”, „No such module”), gdy Xcode przebudowuje swój indeks — zwłaszcza zaraz po regeneracji projektu. To artefakty indeksu, a nie błędy współbieżności. Jeśli xcodebuild zgłasza BUILD SUCCEEDED, model współbieżności jest spełniony, a edytor po prostu nadrabia zaległości1. Gonienie za duchami indeksu to najszybszy sposób, by zmarnować popołudnie na migracji, która już zadziałała.

FAQ

Jak włączyć współbieżność z domyślnym głównym aktorem w Swift 6.2?

Należy ustawić dwa ustawienia kompilacji, oba włączone domyślnie dla nowych projektów Xcode 26: SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor oraz SWIFT_APPROACHABLE_CONCURRENCY = YES5. W pakiecie Swift odpowiadające flagi nadchodzących funkcji ustawia się poprzez swiftSettings, a nie przez ustawienie kompilacji Xcode6.

Co właściwie zmienia SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor?

Sprawia, że kod modułu jest domyślnie izolowany do głównego aktora (SE-0466), więc widok, jego model i jego funkcje pomocnicze są wszystkie @MainActor bez adnotacji2. Współbieżność staje się nazwanym wyjątkiem, po który się sięga, a nie wszechobecnym warunkiem, który adnotuje się w każdej linii.

Czym jest @concurrent i kiedy go używać?

@concurrent przenosi funkcję async do globalnego egzekutora — z premedytacją, z dala od aktora wywołującego4. Przy domyślnym głównym aktorze używa się go do pracy, która w mierzalny sposób potrzebuje wątku w tle (dekodowanie dużego ładunku, zmiana rozmiaru obrazu); jest wzajemnie wykluczające się z @MainActor, więc funkcja albo działa tam, gdzie jej wywołujący, albo celowo od niego ucieka.

Dlaczego dostaję „Main actor-isolated … can not be referenced from a nonisolated context”?

Przy domyślnym głównym aktorze funkcje czyste, statyczne singletony i stałe dziedziczą izolację głównego aktora, a następnie zgłaszają błąd przy wywołaniu z kontekstu nonisolated, takiego jak ewaluacja parametru domyślnego. Poprawką jest precyzyjna adnotacja nonisolated na składowej, a nie wyciszenie; wartość jest bezpieczna do współdzielenia, ponieważ nie dotyka żadnego stanu aktora7.

Czy powinienem używać domyślnego głównego aktora dla celu typu biblioteka lub framework?

Nie. Warstwa sieciowa, parser czy silnik danych nie ma powodu, by domyślnie używać głównego aktora — a robienie tego wymusza @concurrent lub nonisolated na niemal wszystkim. Dla celów bez UI należy pozostawić SWIFT_DEFAULT_ACTOR_ISOLATION nieustawione i izolować z premedytacją, po staremu.

Czy oczekiwanie na funkcję nonisolated async wciąż przeskakuje między aktorami w Swift 6.2?

Nie. SE-0461 sprawia, że funkcja nonisolated async działa na tym aktorze, który ją wywołał, zamiast przeskakiwać do globalnego egzekutora, więc oczekiwanie na nią nie przeciąga cię już przez granicę izolacji ani nie wymaga Sendable na wszystkim w zasięgu3.



  1. Produkcyjny kod autora w aplikacjach iOS rodziny 941 (Ki, Return, Get Bananas), wszystkie wydane z SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor oraz SWIFT_APPROACHABLE_CONCURRENCY = YES. Sześć wzorców błędów i poprawek poniżej stanowiło kompletny zestaw porządkowania ścisłej współbieżności dla Ki 1.0.0. 

  2. Swift Evolution, SE-0466: Control default actor isolation inference. Pozwala modułowi domyślnie przyjąć izolację @MainActor, więc cele UI i aplikacji działają na głównym aktorze, chyba że kod zrezygnuje z tego przez @concurrent lub jawnego aktora. 

  3. Swift Evolution, SE-0461: Run nonisolated async functions on the caller’s actor by default. Funkcja nonisolated async domyślnie przyjmuje nonisolated(nonsending), działając na aktorze wywołującego zamiast przeskakiwać do globalnego egzekutora, co usuwa wymagania Sendable przy przekraczaniu granicy, które towarzyszyły temu przeskokowi. 

  4. Swift Evolution, SE-0461 wprowadza atrybut @concurrent, by funkcja działała na globalnym egzekutorze (wątku w tle). @concurrent i nonisolated(nonsending) to dwa przeciwstawne tryby izolacji dla funkcji nonisolated async: funkcja albo działa tam, gdzie jej wywołujący, albo celowo od niego ucieka. @concurrent nie można łączyć z @MainActor ani niestandardowym aktorem globalnym. 

  5. SWIFT_APPROACHABLE_CONCURRENCY to zbiorcze ustawienie kompilacji Xcode włączające nadchodzące funkcje przystępnej współbieżności (w tym zachowanie SE-0461), a SWIFT_DEFAULT_ACTOR_ISOLATION wybiera domyślną izolację modułu. Nowe projekty Xcode 26 włączają oba z domyślnym głównym aktorem. Udokumentowane u Donny’ego Walsa, „Exploring concurrency changes in Swift 6.2”, oraz u Paula Hudsona, „What’s new in Swift 6.2”, oba zestawione krzyżowo z leżącymi u podstaw propozycjami SE-0461 i SE-0466. 

  6. Swift, Swift Concurrency Migration Guide, „Enabling Complete Concurrency Checking” i konfiguracja trybu językowego. W pakiecie Swift domyślną izolację oraz funkcje przystępnej współbieżności ustawia się poprzez flagi nadchodzących funkcji w swiftSettings, a nie przez ustawienie kompilacji Xcode. 

  7. Swift, Migration Guide: global actor isolation and nonisolated. Metody typu @MainActor dziedziczą izolację głównego aktora; nonisolated wyłącza metodę z tego, co jest poprawne dla funkcji czystych, które nie dotykają żadnego izolowanego stanu. 

  8. Apple Developer, MainActor.assumeIsolated(_:). Potwierdza, że bieżące wykonanie jest już na głównym aktorze, i uruchamia domknięcie synchronicznie bez przeskoku między aktorami. Asercja zatrzymuje program w czasie wykonania, jeśli niezmiennik nie zachodzi, więc jest poprawna tylko tam, gdzie wywołujący na pewno działa na głównym wątku. 

  9. Apple, WWDC 2026 Swift Group Lab. Wypowiedzi przypisane Holly i Dougowi z zespołu rdzeniowego Swifta są sparafrazowane z nagrania spisanego lokalnie; Apple nie opublikowało oficjalnej transkrypcji ani napisów do tej sesji, więc sformułowanie jest przybliżone, a nie cytatem dosłownym. 

Powiązane artykuły

Co zespół Swift z Apple powiedział podczas labu na WWDC26

Swift Group Lab Apple na WWDC26 odbył się bez napisów. Transkrypcję wykonano lokalnie. Szczere odpowiedzi inżynierów o w…

11 min czytania

Co nowego w Swift (2026): aktualizacja WWDC26

Swift 6.3 i 6.4 z WWDC26: dostępność anyAppleOS, selektory modułów, akcesory borrow/mutate, protokół Iterable, interoper…

15 min czytania

Dwa serwery MCP uczyniły Claude Code systemem budowania iOS

XcodeBuildMCP i serwer Xcode MCP od Apple zapewniają Claude Code ustrukturyzowany dostęp do kompilacji, testów i debugow…

14 min czytania