.agents/skills/swift-concurrency/references/linting.md
Use this when:
async_without_await or other concurrency-related warnings.Skip this file if:
actors.md, sendable.md, or threading.md.Jump to:
async_without_await RuleSwiftLint provides several rules targeting async/await and concurrency patterns. Understanding when to fix vs. suppress is critical.
| Rule | Default | Purpose |
|---|---|---|
async_without_await | warning | Flags async functions that never await |
unowned_variable_capture | warning | Warns about unowned in closures (risky in async) |
class_delegate_protocol | warning | Ensures delegates are class-bound (AnyObject) |
weak_delegate | warning | Delegates should be weak to avoid retain cycles |
async_without_awaitasync if it never awaits.await Task.yield(), await Task { ... }.value). Those mask the real issue and add meaningless suspension points.Task.yield(): OK in tests or scheduling control when you truly need a yield; not as a lint workaround.asyncasync.async.@concurrent requirement — stays async even without await.async — no caller needs async semantics.async (and adjust call sites) when no async semantics are needed.async is required (protocol/override/@concurrent):
async and narrowly suppress the rule where appropriate (common for mocks/stubs/overrides).// swiftlint:disable:next async_without_await
func fetch() async { perform() }
// For a block:
// swiftlint:disable async_without_await
func makeMock() async { perform() }
// swiftlint:enable async_without_await
async is truly required (protocol/override/@concurrent).async and update callers.The Swift compiler generates concurrency-related warnings based on strict concurrency checking level.
"Capture of non-sendable type"
// Warning: Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure
Task {
self.doWork() // 'self' is non-Sendable
}
Fixes (in order of preference):
Sendable if it's truly thread-safe@MainActor isolation if it's UI-relatedself@unchecked Sendable with documented safety invariant (last resort)"Non-sendable result returned"
// Warning: Non-sendable type 'MyResult' returned by implicitly async call
let result = await actor.getData() // Returns non-Sendable type
Fixes:
"Main actor-isolated property accessed from non-isolated context"
// Warning: Main actor-isolated property 'title' cannot be referenced from a non-isolated context
func updateTitle() {
viewModel.title = "New" // viewModel is @MainActor
}
Fixes:
@MainActorawait MainActor.run { } for one-off accessFix when:
Suppress when:
// Suppress Sendable warnings for legacy imports
@preconcurrency import LegacyFramework
// Suppress for a single declaration
nonisolated(unsafe) var legacyCallback: (() -> Void)?
// Type-level suppression (use sparingly)
struct LegacyWrapper: @unchecked Sendable {
// Document why this is safe
private let lock = NSLock()
private var value: Int
}
When using suppression annotations, document:
/// Thread-safe: Internal lock protects all mutations.
/// TODO: Remove @unchecked when migrated to actor (JIRA-1234)
final class ThreadSafeCache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Data] = [:]
}