my-ios-app-swift-concurrency — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited my-ios-app-swift-concurrency (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Review, fix, and write concurrent Swift code targeting Swift 6.3+. Apply actor isolation, Sendable safety, and modern concurrency patterns with minimal behavior changes.
@Observable and ConcurrencyWhen diagnosing a concurrency issue, follow this sequence:
MainActor.Complete / Targeted / Minimal only when auditing Swift 5 migration settings.
@MainActor, custom actor,nonisolated) and whether a default isolation mode is active.
Prefer edits that preserve existing behavior while satisfying data-race safety.
| Situation | Recommended fix |
|---|---|
| UI-bound type | Annotate the type or relevant members with @MainActor. |
| Protocol conformance on MainActor type | Use an isolated conformance: extension Foo: @MainActor Proto. |
| Global / static state | Protect with @MainActor or move into an actor. |
| Background work needed | Use a @concurrent async function on a nonisolated type. |
| Sendable error | Prefer immutable value types. Add Sendable only when correct. |
| Cross-isolation callback | Use sending parameters (SE-0430) for finer control. |
@unchecked Sendable or nonisolated(unsafe) was added.Swift 6.2 introduces "approachable concurrency" -- a set of language changes that make concurrent code safer by default while reducing annotation burden. In Xcode, Approachable Concurrency and Default Actor Isolation are separate build settings: use Approachable Concurrency for the bundled upcoming-feature flags, and set Default Actor Isolation to MainActor when you want unannotated code inferred as @MainActor.
With the -default-isolation MainActor compiler flag, SwiftPM .defaultIsolation(MainActor.self), or Xcode's Default Actor Isolation setting set to MainActor, unannotated declarations in the module are inferred as @MainActor unless explicitly opted out.
Effect: Eliminates most data-race safety errors for UI-bound code and global/static state without writing @MainActor everywhere.
// With default MainActor isolation enabled, these are implicitly @MainActor:
final class StickerLibrary {
static let shared = StickerLibrary() // safe -- on MainActor
var stickers: [Sticker] = []
}
final class StickerModel {
let photoProcessor = PhotoProcessor()
var selection: [PhotosPickerItem] = []
}
// Conformances are also implicitly isolated:
extension StickerModel: Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}When to use: Recommended for apps, scripts, and other executable targets where most code is UI-bound. Not recommended for library targets that should remain actor-agnostic.
Nonisolated async functions now stay on the caller's actor by default instead of hopping to the global concurrent executor. This is the nonisolated(nonsending) behavior.
class PhotoProcessor {
func extractSticker(data: Data, with id: String?) async -> Sticker? {
// In Swift 6.2+, this runs on the caller's actor (e.g., MainActor)
// instead of hopping to a background thread.
// ...
}
}
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else {
return nil
}
// No data race -- photoProcessor stays on MainActor
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}Use @concurrent to explicitly request background execution when needed.
@concurrent Attribute@concurrent ensures a function always runs on the concurrent thread pool, freeing the calling actor to run other tasks.
class PhotoProcessor {
var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] { return sticker }
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
@concurrent
static func extractSubject(from data: Data) async -> Sticker {
// Expensive image processing -- runs on background thread pool
// ...
}
}To move a function to a background thread:
nonisolated (or the function itself is).@concurrent to the function.async if not already asynchronous.await at call sites.nonisolated struct PhotoProcessor {
@concurrent
func process(data: Data) async -> ProcessedPhoto? { /* ... */ }
}
// Caller:
processedPhotos[item.id] = await PhotoProcessor().process(data: data)Task.immediate starts executing synchronously on the current actor before any suspension point, rather than being enqueued.
Task.immediate { await handleUserInput() }Use for latency-sensitive work that should begin without delay. There is also Task.immediateDetached which combines immediate start with detached semantics.
Observations { } provides async observation of @Observable types via AsyncSequence, enabling transactional change tracking.
for await _ in Observations { model.count } {
print("Count changed to \(model.count)")
}A conformance that needs MainActor state is called an isolated conformance. The compiler ensures it is only used in a matching isolation context.
protocol Exportable {
func export()
}
// Isolated conformance: only usable on MainActor
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // OK -- ImageExporter is on MainActor
}
}If ImageExporter were nonisolated, adding a StickerModel would fail: "Main actor-isolated conformance of 'StickerModel' to 'Exportable' cannot be used in nonisolated context."
ContinuousClock and SuspendingClock now expose .epoch (SE-0473), enabling instant comparison and conversion between clock types.
let continuous = ContinuousClock()
let elapsed = continuous.now - continuous.epoch // Duration since system boot@MainActor for all UI-touching code. No exceptions.nonisolated only for methods that access immutable (let) propertiesor are pure computations.
@concurrent to explicitly move work off the caller's actor.nonisolated(unsafe) unless you have proven internalsynchronization and exhausted all other options.
NSLock, DispatchSemaphore) inside actors.Sendable when all storedproperties are Sendable.
Sendable.@MainActor classes are implicitly Sendable. Do NOT add redundantSendable conformance.
final with all stored properties let andSendable.
@unchecked Sendable is a last resort. Document why the compiler cannotprove safety.
sending parameters (SE-0430) for finer-grained isolation control.@preconcurrency import only for third-party libraries you cannotmodify. Plan to remove it.
defer blocks can now contain await (SE-0493). Use for async cleanup — closing connections, flushing buffers, or releasing resources that require an async call.
func fetchData() async throws -> Data {
let connection = try await openConnection()
defer { await connection.close() }
return try await connection.read()
}Task: Unstructured, inherits caller context.
Task { await doWork() }Task.detached: No inherited context. Use only when you explicitly need to break isolation inheritance.
Task.immediate: Starts immediately on current actor. Use for latency-sensitive work.
Task.immediate { await handleUserInput() }async let: Fixed number of concurrent operations.
async let a = fetchA()
async let b = fetchB()
let result = try await (a, b)TaskGroup: Dynamic number of concurrent operations.
try await withThrowingTaskGroup(of: Item.self) { group in
for id in ids {
group.addTask { try await fetch(id) }
}
for try await item in group { process(item) }
}Task.isCancelled or calltry Task.checkCancellation() in loops.
.task modifier in SwiftUI -- it handles cancellation on view disappear.withTaskCancellationHandler for cleanup.deinit or onDisappear.Actors are reentrant. State can change across suspension points.
// WRONG: State may change during await
actor Counter {
var count = 0
func increment() async {
let current = count
await someWork()
count = current + 1 // BUG: count may have changed
}
}
// CORRECT: Mutate synchronously, no reentrancy risk
actor Counter {
var count = 0
func increment() { count += 1 }
}Use AsyncStream to bridge callback/delegate APIs:
let stream = AsyncStream<Location> { continuation in
let delegate = LocationDelegate { location in
continuation.yield(location)
}
continuation.onTermination = { _ in delegate.stop() }
delegate.start()
}Use withCheckedContinuation / withCheckedThrowingContinuation for single-value callbacks. Resume exactly once.
@Observable and Concurrency@Observable classes should be @MainActor for view models.@State to own an @Observable instance (replaces @StateObject).Observations { } (SE-0475) for async observation of @Observableproperties as an AsyncSequence.
When actors are not the right fit — synchronous access, performance-critical paths, or bridging C/ObjC — use low-level synchronization primitives:
Synchronization module): Preferred lock fornew code. Stores protected state inside the lock. withLock { } pattern.
os module): Use when targetingolder iOS versions. Supports ownership assertions for debugging.
Synchronization module): Lock-free atomicsfor simple counters and flags. Requires explicit memory ordering.
Key rule: Never put locks inside actors (double synchronization), and never hold a lock across await (deadlock risk). See references/synchronization-primitives.md for full API details, code examples, and a decision guide for choosing locks vs actors.
@MainActor freezes UI.Move to a @concurrent function.
do not need @MainActor. Only UI-touching code does.
plain struct or function.
Sendable struct, not an actor.and cancellation propagation.
Task references and cancel them, oruse the .task view modifier.
[weak self] when capturing self inlong-lived stored tasks.
DispatchSemaphore.wait() in async codewill deadlock. Use structured concurrency instead.
@MainActor and nonisolated properties in onetype. Isolate the entire type consistently.
@MainActor funcover await MainActor.run { }.
@MainActorSendable conformance is correct (no unjustified @unchecked)@preconcurrency imports are documented with removal plan@concurrent, not @MainActor.task modifier used in SwiftUI instead of manual Task management~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.