Coding Standards Enforcer
When to use
Use this skill whenever you add, modify, or review Swift code in this repo to ensure compliance with concurrency, language usage, and Swift 6 migration rules.
Workflow
- Identify the files and changes in scope.
- Scan for violations of the rules below.
- Apply fixes or call out deviations explicitly.
4. Concurrency & Threading
- Strict Concurrency: Swift 6.2 defaults to
@MainActorisolation for Views and UI logic. Assume strict isolation checks are active. Everything is@MainActorby default. - Background Tasks: Move heavy physics/data work off the main actor using
@concurrentfunctions or dedicated actors. - Task Management: Cancel long-running tasks on teardown.
Swift Concurrency Guidelines
Core mental model
Think in isolation domains rather than threads:
MainActoris the UI lane and must own UI state.actortypes protect their own mutable state.nonisolatedcode is shared and cannot touch actor state.Sendabletypes are safe to move across domains.
Async and parallel work
func fetchUser(id: Int) async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
async let avatar = fetchImage("avatar.jpg")
async let banner = fetchImage("banner.jpg")
let profile = Profile(avatar: try await avatar, banner: try await banner)
Tasks and task groups
.task { avatar = await downloadAvatar() }
Task { await saveProfile() }
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { avatar = try await downloadAvatar() }
group.addTask { bio = try await fetchBio() }
try await group.waitForAll()
}
Isolation domains
@MainActor
final class ViewModel {
var items: [Item] = []
}
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) { balance += amount }
}
Approachable concurrency settings (Swift 6.2+)
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorkeeps UI code on the main actor by default.SWIFT_APPROACHABLE_CONCURRENCY = YESkeeps nonisolated async on the caller's actor.
@concurrent func processLargeFile() async { }
Sendable
struct User: Sendable {
let id: Int
let name: String
}
final class ThreadSafeCache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Data] = [:]
}
Isolation inheritance
Task { }inherits the current actor.Task.detached { }does not inherit isolation and should be rare.
Common mistakes to avoid
- Treating
asyncas automatic background work. - Creating many actors when
@MainActoris sufficient. - Using
MainActor.runwhen the enclosing function can be annotated. - Blocking async code with
DispatchSemaphoreorDispatchGroup.wait(). - Spawning unstructured
Taskinstances instead ofasync letor task groups.
Quick reference
asyncandawaitfor suspension points.Task { }for structured async work.actorfor isolated mutable state.@MainActorfor UI-bound work.Sendablefor cross-actor data transfer.
7. Swift Language Standards
- Observable Classes:
@Observableclasses are@MainActorby default, so explicit@MainActorannotation is not needed. - Swift-Native APIs: Prefer Swift-native alternatives to Foundation methods where they exist, such as using
replacing("hello", with: "world")with strings rather thanreplacingOccurrences(of: "hello", with: "world"). - Modern Foundation API: Prefer modern Foundation API, for example
URL.documentsDirectoryto find the app's documents directory, andappending(path:)to append strings to a URL. - Number Formatting: Never use C-style number formatting such as
Text(String(format: "%.2f", abs(myNumber))); always useText(abs(change), format: .number.precision(.fractionLength(2)))instead. - Static Member Lookup: Prefer static member lookup to struct instances where possible, such as
.circlerather thanCircle(), and.borderedProminentrather thanBorderedProminentButtonStyle(). - Modern Concurrency: Never use old-style Grand Central Dispatch concurrency such as
DispatchQueue.main.async(). If behavior like this is needed, always use modern Swift concurrency. - Text Filtering: Filtering text based on user-input must be done using
localizedStandardContains()as opposed tocontains(). - Force Unwraps: Avoid force unwraps and force
tryunless it is unrecoverable.