DevOps / CI·CD 24. April 2026

2026-04-24 Swift 6 Strict Concurrency, Sendable & @MainActor in xcodebuild-CI auf gemietetem Cloud-Mac

MacXCode Engineering-Team 24. April 2026 ~19 Min. Lesezeit

iOS- und macOS-Build-Verantwortliche, die Apple-Silicon-Hosts in HK / JP / KR / SG / US bereits eingeführt haben, stehen vor einem zweiten Hebel: das Swift-6-Sprachmodell mit Strict-Concurrency-Checking verwandelt „läuft auf meiner Maschine“ in eine messbare Differenz zwischen warmem Laptop und kaltem, parallelisiertem xcodebuild-Job auf einem gemieteten SSH-Mac. Dieses Runbook vom 2026-04-24 ist kein Sprach-Tutorial—es kartiert Build-Settings (SWIFT_STRICT_CONCURRENCY, Swift-Sprachversion), DerivedData- und Index-Store-Isolation sowie eine gestufte Migration für Sendable- / nonisolated- / MainActor-Rauschen, das in parallelen und Self-hosted-Runner-Topologien explodiert. Verknüpfen Sie mit tmp DerivedData + xcresult-Isolation und Remote-Archive, damit derselbe Node in Singapur keinen geteilten ModuleCache zertrampelt, den Ihr US-Shard für privat hielt.

Warum Swift-6-Strict-Concurrency in headless CI anders reagiert

Strikte Prüfung legt Data Races offen, die der Simulator nie auslöst, weil der UI-Hauptthread die Lebensdauer eines erfassten var maskiert. In CI parallelisierte swift-frontend-Jobs und Whole-Module-Optimierungen ordnen Diagnosen neu; dasselbe Package, das über Nacht auf einem 64 GB-Dev-Laptop kompilierte, kann in einer -warnings-as-errors-Lane scheitern, wenn inkrementell aus ist. Behandeln Sie Strict Concurrency wie archiviertes Bitcode-Ende oder Parität CLT vs. voll Xcode: eine separate Pipeline-Stufe mit gepinntem DEVELOPER_DIR und schriftlicher Policy, welche Targets auf SWIFT_STRICT_CONCURRENCY=targeted bleiben dürfen, bis Refactors landen.

Planen Sie zusätzlich Index-Aufbauten ein: Beim ersten Swift-6-Vollbuild auf leerem DerivedData können ExtractAppIntentsMetadata und benachbarte Schritte spürbar länger laufen, wenn Ihre App App Intents oder größere WidgetKit-Extensions liefert. Der Wall-Clock-Sprung ist kein Concurrency-Defekt, sondern ein Kalt-Cache-Artefakt—kommunizieren Sie es an Stakeholder wie die erste SwiftPM-Registry-Aufwärmung. Halten Sie p95 vorher/nachher im Dashboard fest, damit die „Swift-6-Woche“ nicht wie eine Kapazitätsregression in der Miet-Cloud aussieht.

Build-Settings: Sprachversion und Strict-Concurrency-Modi

Auf Projekt- oder xcconfig-Ebene drei Regler ausrichten: (1) SWIFT_VERSION auf die Swift-6-Toolchain, die Sie ausliefern; (2) SWIFT_STRICT_CONCURRENCY als complete in Ihrer merge-blockierenden Lane, aber vielleicht targeted für Legacy-Vendor-Static-Libs, bis Sie annotieren können; (3) die pro-Target-Default-Isolation in Swift 6, wo UIKit- und SwiftUI-Module oft explizite @MainActor-Abdeckung wollen. In CI-YAML Overrides nur setzen, wenn das Scheme sie noch nicht tragen kann, z. B. OTHER_SWIFT_FLAGS=-warn-concurrency in einer Advisory-Lane, nicht still in Release-Archive gemischt, die bitweise reproduzierbar bleiben müssen. Unterschied zwischen Package-Plugin und App-Target dokumentieren: Plugin-Build-Logs können Fehler verstecken, bis Sie -strict-concurrency=complete für die relevante Binary aktivieren.

xcodebuild-Aufruf: Archive vs. Build vs. Analyze

Einen einzigen DEVELOPER_DIR=/Applications/Xcode.app auf dem Host pinnen, dann explizite -configuration und -destination nutzen, um Scheme-Drift zu vermeiden. Für ein striktes Nur-Compile-Gate vor UI-Tests xcodebuild -scheme App build -destination 'platform=iOS Simulator,name=iPhone 16' mit CODE_SIGNING_ALLOWED=NO bevorzugen, wenn Sie nur Compiler-Signale brauchen—dann ein zweiter Job für Simulator-Tests mit wieder aktiviertem Signing, analog zur Aufteilung im XCTest + xcresult-Guide. Minimales Strict-Gate:

DEVELOPER_DIR=/Applications/Xcode.app xcodebuild build -workspace App.xcworkspace -scheme App -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 16' -derivedDataPath "$CI_DERIVED/Swift6Gate" OTHER_SWIFT_FLAGS='$(inherited) -warn-concurrency'

CI-Leitplanken: -Retry nur für Infra-Flakes, nicht für Concurrency-Fehler—einen zweiten identischen Fehler bei Sendable als echten Code-Defekt behandeln, nicht als Xcode-Geist.

Parallel-Lanes, DerivedData und Index Store

Wenn Sie auffächern (siehe M4-Auffächern), geben Sie jedem Child-Job ein privates -derivedDataPath und erwägen Sie, inkrementell für die Strict-Stufe abzuschalten, damit Diagnosen über HK / JP / KR / SG / US-Builder deterministisch sind. Geteiltes DerivedData auf NFS-ähnlichen Mounts ist der schnellste Weg zu Phantomfehlern „cannot assign through subscript: base is not a concurrent value“, die beim Retry verschwinden—genau das Flake-Muster, vor dem der Isolations-Artikel schon für xcresult warnt. Wenn Sie aus Kostengründen ein Volume teilen müssen, legen Sie wenigstens Index/DataStore pro Job mit Präfix $(CI_JOB_ID) an.

Migrationsmatrix: Warnungen, Fehler und Owner

Signal Aktion Owner
Sendable-Closure-Capture Auf Value-Types refaktorisieren, Typen Sendable markieren oder in Actor auslagern Feature-Squad
MainActor-Isolations-Drift UI-Updates über eine kleine Fassade leiten Client-Lead
3P-Binary ohne Sendable Modulgrenze wrappen, Vendor-Issue pinnen oder forken Build-Plattform

Wenn es kein Concurrency-Fehler ist

Manche Failures tarnen sich als Swift-6-Themen: Provisioning und Keychain-Hüpfer während CodeSign können sich in Logs mit swift-frontend-Zeilen verzahnen; dSYM-Lücken nach einem Archive brechen die Symbolikation, nicht den Compiler. Halten Sie dSYM-Hygiene in derselben Wochenreview wie Strict-Lanes, sonst jagt der On-Call den falschen Diff.

Signing, Entitlements und „Strict“ im selben Build

Mehr Compiler-Checks im selben Release-Zug wie eine Provisioning-Rotation einzuschalten ist riskant. Sequenz: (1) Signing auf einem Wegwerf-Canary grün beweisen, (2) Strict-Stufe aktivieren, (3) erst dann ein Feature-Flag mergen, das SWIFT_STRICT_CONCURRENCY=complete im Default-Scheme erzwingt. Derselbe SSH-Mac mini M4-Pool kann beides hosten, aber Jobs sollten sich nicht in einem Workspace-Ordner verzahnen—Parallelität ist nur dann Stärke, wenn die Archive- und Sim-Docs bereits befolgt werden.

Start bei SwiftPM + Registry-Cache und Ruby + CocoaPods-Determinismus, wenn ein Concurrency-Failure eigentlich ein Dependency-Graph-Mismatch ist. Wenn Teams Xcode Cloud und dedizierte Hosts mischen, Sprach-Flags auf beiden ausrichten, sonst blockiert die dedizierte Strict-Lane Merges, die Xcode Cloud nie sah.

FAQ: Swift 6 auf geteilten Buildern

Frage Praktische Antwort
Zuerst für SPM-Packages aktivieren? Oft ja—Grenzen abbilden, dann App-Targets; Package-Traits nutzen, wenn Test-only-Code getrennt wird.
Ist VNC nötig? Meist nein—VNC ist Break-Glass fürs visuelle Debuggen, nicht für Strict-Build-Logs.
Was ist mit Platte für den Index? 1–2 TB-Hosts wählen, wenn lange SwiftUI-Previews aus bleiben, aber große Index-Bäume bleiben; bei Preisen schauen, wenn Strict-Lanes Ihren Artefakt-Churn verdreifacht.

Warum Mac mini M4 Bare Metal zu concurrency-lastigen Kompilaten passt

Strikte Prüfung ist CPU- und I/O-intensiv: Der Compiler schreibt Graphen um, der Index wächst, und der Index Store profitiert vom selben NVMe, den Sie schon für Archive auf einem gemieteten MacXCode-Host budgetieren. Ein Socket-Stack ohne Noisy-Neighbor-Effekte senkt auch Tail-Latency, wenn eine Lane während der Paketauflösung mit remote Cache oder Registry spricht. Wenn Ihr Strict-Pipeline stabil ist, skalieren Sie einen zweiten Mac mini M4 in derselben Region, bevor Sie Lanes wieder auf geteiltes DerivedData zusammenziehen—Ihr 2026-Mainline verdient deterministisches Signal, kein Lotterie-Build.

Swift-6-Sauberbuilds auf M4 fahren

NVMe · Isolierte Lanes · Globale Regionen