Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Swift 6 concurrency patterns. Use when working with async/await, actors, MainActor isolation, or Sendable conformance.
Skill: Concurrency
Guide for Swift 6 concurrency patterns used in this project.
When to use this skill
Work with async/await code
Create actors for thread-safe state
Understand MainActor isolation
Fix Sendable conformance issues
Project Configuration
This project uses Swift 6 with special build settings:
Setting
Value
Effect
SWIFT_APPROACHABLE_CONCURRENCY
YES
Automatic Sendable inference
SWIFT_DEFAULT_ACTOR_ISOLATION
MainActor
All types MainActor-isolated by default
Exception:ChallengeNetworking overrides SWIFT_DEFAULT_ACTOR_ISOLATION to nonisolated at the target level. All networking types are nonisolated by default — no nonisolated annotations needed. See the Networking README.
Default MainActor Isolation
With SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, all types are MainActor-isolated by default.
What this means
No need for explicit @MainActor on ViewModels, Views, or UI-related types
Types that need to run off the main thread must opt out using nonisolated
// These are automatically MainActor-isolatedfinalclassCharacterListViewModel { } // No @MainActor neededstructCharacterListView: View { } // No @MainActor needed
Approachable Concurrency (Automatic Sendable)
With SWIFT_APPROACHABLE_CONCURRENCY = YES, the compiler automatically infers Sendable conformance:
// This struct is automatically Sendable (all properties are Sendable)structUser: Equatable {
let id: Intlet name: String
}
// No need to write:// struct User: Equatable, Sendable { ... }
Rules:
Structs with all Sendable properties are implicitly Sendable
Enums with Sendable associated values are implicitly Sendable
Do not explicitly mark types as Sendable (it's inferred)
Opting Out of MainActor Isolation
Types that need to run off the main thread must explicitly opt out.
Actors (custom isolation domain)
Actors have their own isolation domain (not MainActor):
// Actors are NOT MainActor-isolatedactorCharacterMemoryDataSource {
privatevar storage: [Int: CharacterDTO] = [:]
funcsave(_character: CharacterDTO) {
storage[character.id] = character
}
funcget(id: Int) -> CharacterDTO? {
storage[id]
}
}
Framework subclasses called from background threads
// XCTestCase subclasses need nonisolated for XCTest compatibilitynonisolatedfinalclassCharacterFlowUITests: XCTestCase {
overridefuncsetUpWithError() throws {
continueAfterFailure =false
}
@MainActorfunctestCharacterFlow() throws {
let app =XCUIApplication()
app.launch()
// ...
}
}
nonisolated types (pure data types)
Types that are pure data with no UI concern should be nonisolated:
// Internal networking envelope — nonisolated via module default (ChallengeNetworking)structGraphQLResponse<T: Decodable>: Decodable {
let data: T?
let errors: [GraphQLResponseError]?
}
All members (properties, synthesized conformances) become nonisolated automatically.
Note: In ChallengeNetworking, types are nonisolated by default (module-level override). In other modules, use the nonisolated keyword explicitly.
Nonisolated Data/Domain layer
The entire Data and Domain layer uses explicit nonisolated annotations. This ensures Data layer work (network I/O, JSON decoding, mapping) runs off MainActor when combined with @concurrent:
@concurrent guarantees an async function runs on the generic executor (thread pool), not on any actor. Use it for CPU-intensive work like JSON decoding + network I/O.
Transport clients (HTTPClient, GraphQLClient) — JSON decode + network I/O happen here
Repository contracts and implementations — ensures Data layer runs off MainActor
Remote DataSource contracts and implementations — defensive guarantee of background execution
When NOT to use:
Actors — @concurrent cannot be used with actor isolation (SE-0461)
UseCases — trivial coordination, stay MainActor
CachePolicy.fetch — nonisolated method on enum, delegates to @concurrent repos/datasources
Implementation notes:
In modules with MainActor default: private helpers called from @concurrent methods must be nonisolated
ChallengeNetworking uses nonisolated default — helpers, types, and inits are nonisolated automatically
Types constructed inside @concurrent methods need nonisolated init (e.g., Endpoint — already nonisolated via module default)
Why both nonisolated and @concurrent are required
They are complementary — each solves a different problem:
Annotation
Purpose
Without it
nonisolated
Removes MainActor isolation from the type/method
@concurrent on a MainActor-isolated method is a compile error — contradicts "runs on MainActor"
@concurrent
Executes on the cooperative thread pool
nonisolated async inherits the caller's executor (SE-0338) — runs on MainActor if called from MainActor
The flow with SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor:
MainActor (default)
→ nonisolated → inherits caller's executor (SE-0338)
→ nonisolated + @concurrent → runs on thread pool (SE-0461)
nonisolated is the prerequisite for @concurrent. You cannot use @concurrent without first removing the actor isolation.
Types without async methods (DTOs, Mappers, Domain Models) also need nonisolated because:
They are created/used inside@concurrent methods (repos, datasources)
A MainActor-isolated init cannot be called from a @concurrent context
Without nonisolated, passing them between contexts requires unnecessary actor hops
// Without nonisolated on CharacterMapper:@concurrentfuncgetCharacter(...) asyncthrows -> Character {
let dto =tryawait remoteDataSource.fetchCharacter(...)
return mapper.map(dto) // mapper.map() is MainActor-isolated → compile error
}
State Management
Use @Observable (iOS 17+), notObservableObject:
// REQUIRED - Use @Observable@ObservablefinalclassCharacterListViewModel {
var state: CharacterListViewState= .idle
}
// PROHIBITED - Never use ObservableObject/@PublishedfinalclassCharacterListViewModel: ObservableObject {
@Publishedvar state: CharacterListViewState= .idle
}
Rules:
Stateful ViewModels use @Observable macro (stateless ViewModels with no observable state are plain final class)
No ObservableObject protocol conformance
No @Published property wrappers
Views use @State to hold @Observable instances
Prohibited Patterns
The following patterns are prohibited in this project:
// PROHIBITED - Never use these patternsDispatchQueue.main.async { ... }
DispatchQueue.global().async { ... }
completionHandler: @escaping (Result<T, Error>) -> VoidObservableObject/@Published// Use @Observable insteadNotificationCenterforasync events
Combinefor new code
Required Patterns
Always use modern Swift concurrency:
// REQUIRED - Use async/awaitfuncfetchData() asyncthrows -> Data {
let (data, _) =tryawaitURLSession.shared.data(from: url)
return data
}
// REQUIRED - Use Task for bridgingTask {
await performAsyncWork()
}
// REQUIRED - Use actors for shared mutable stateactorDataStore {
privatevar cache: [String: Data] = [:]
funcstore(_data: Data, forKeykey: String) {
cache[key] = data
}
}
Common Patterns by Type
Type
Isolation
Notes
View
MainActor (default)
No annotation needed
ViewModel
MainActor (default)
No annotation needed
UseCase
MainActor (default)
No annotation needed
Container
MainActor (default)
No annotation needed
Repository contract
nonisolated protocol
@concurrent on methods, explicit Sendable
Repository impl
nonisolated struct
@concurrent on methods
RemoteDataSource contract
nonisolated protocol
@concurrent on methods, explicit Sendable
RemoteDataSource impl
nonisolated struct
@concurrent on methods
DTO
nonisolated struct
Pure data, Decodable + Equatable
Mapper
nonisolated struct
Stateless, MapperContract
Domain Model
nonisolated struct
Pure data with behavior
Domain Error
nonisolated enum
nonisolated on extensions too
CachePolicy
nonisolated enum
Shared across features; carries fetch behavior via sending closures
HTTPClient / GraphQLClient
nonisolated (module default)
@concurrent on public methods
Endpoint / HTTPMethod
nonisolated (module default)
Pure data types, keep explicit Sendable for cross-module use
Swift actors are reentrant by design. When an actor-isolated function suspends at an await, other tasks can execute on the same actor before the original function resumes. This is called interleaving.
The problem
Every await inside an actor is a suspension point where actor state can change:
// DANGEROUS — reentrancy can break invariantsactorImageDiskCache: ImageDiskCacheContract {
privatelet fileSystem: FileSystemContract// `: Actor`funcimage(forurl: URL) async -> UIImage? {
guardlet data =try?await fileSystem.contents(at: fileURL) else {
returnnil
}
// ⚠️ SUSPENSION POINT — another task can run here (e.g., eviction deletes the file)guardlet attributes =try?await fileSystem.fileAttributes(at: fileURL) else {
// File was deleted between the two awaits!returnnil
}
// ...
}
}
Between two await calls on the same actor, another task (e.g., eviction) can interleave and modify the actor's state or the underlying filesystem. This leads to:
Stale reads: data read before suspension may not match state after resumption
Broken invariants: multi-step operations are no longer atomic
Redundant or conflicting operations: concurrent evictions interleaving
The solution: eliminate suspension points
If an actor's dependency is Sendable with nonisolated methods instead of an Actor, its calls execute synchronously within the caller actor's isolation — no await, no suspension, no interleaving:
// SAFE — zero suspension points, every method is an atomic critical sectionprotocolFileSystemContract: Sendable {
nonisolatedfunccontents(aturl: URL) throws -> Datanonisolatedfuncwrite(_data: Data, tourl: URL) throws// ...
}
structFileSystem: FileSystemContract {
// FileManager is not Sendable but is documented as thread-safe.// Safe to use from any isolation domain without synchronization.nonisolated(unsafe) privatelet fileManager: FileManagernonisolatedfunccontents(aturl: URL) throws -> Data {
tryData(contentsOf: url)
}
// ...
}
actorImageDiskCache: ImageDiskCacheContract {
privatelet fileSystem: FileSystemContractfuncimage(forurl: URL) -> UIImage? { // No `async` — fully synchronousguardlet data =try? fileSystem.contents(at: fileURL) else { returnnil }
// No suspension point — no other task can interleave hereguardlet attributes =try? fileSystem.fileAttributes(at: fileURL) else { ... }
// ...
}
}
When to use each pattern
Pattern
Use when
Example
: Actor protocol
Dependency has its own mutable state to protect
MemoryDataSource, UserDefaultsDataSource
: Sendable + nonisolated
Dependency is a stateless wrapper around a thread-safe API
FileSystem (wraps FileManager)
nonisolated is mandatory on protocol methods
With SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, protocol methods withoutnonisolated are MainActor-isolated by default. Calling them from a non-MainActor actor requires await for the MainActor hop — reintroducing suspension points.
nonisolated(unsafe) on the property does NOT bypass method isolation — it only affects property access:
actorImageDiskCache {
nonisolated(unsafe) privatelet fileSystem: FileSystemContract// ❌ fileSystem.contents(at:) is still MainActor-isolated per protocol// ❌ Compiler error: "Call to main actor-isolated instance method in a synchronous actor-isolated context"
}
Thread-safe non-Sendable types
FileManager and UserDefaults are thread-safe but not Sendable. Use nonisolated(unsafe) to store them:
structFileSystem: FileSystemContract {
// FileManager is not Sendable but is documented as thread-safe.// Safe to use from any isolation domain without synchronization.nonisolated(unsafe) privatelet fileManager: FileManager
}
Mock pattern for Sendable protocols with nonisolated methods
This is safe in practice because the actor serializes all calls to the mock. Tests configure the mock on MainActor (setup) and verify on MainActor (assertions) — no concurrent access.
Checklist
Async functions use async throws (not completion handlers)
Actors are used for shared mutable state
No DispatchQueue usage
No explicit Sendable conformance (it's inferred) — exception: public nonisolated types crossing module boundaries keep explicit Sendable because inference doesn't cross modules
No explicit @MainActor on ViewModels/Views (it's default)