Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Diagnose and fix Swift Concurrency issues: async/await, actor isolation, Sendable, and Swift 6 migration.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/linting.md
1# Linting & Concurrency23Use this when:45- SwiftLint flags `async_without_await` or other concurrency-related warnings.6- You need to decide whether to suppress, fix, or reconfigure a concurrency lint rule.78Skip this file if:910- The issue is a compiler diagnostic, not a lint rule. Use `actors.md`, `sendable.md`, or `threading.md`.1112Jump to:1314- SwiftLint Concurrency Rules Overview15- `async_without_await` Rule16- Suppression Strategies1718## SwiftLint Concurrency Rules Overview1920SwiftLint provides several rules targeting async/await and concurrency patterns. Understanding when to fix vs. suppress is critical.2122| Rule | Default | Purpose |23|------|---------|---------|24| `async_without_await` | warning | Flags `async` functions that never await |25| `unowned_variable_capture` | warning | Warns about `unowned` in closures (risky in async) |26| `class_delegate_protocol` | warning | Ensures delegates are class-bound (AnyObject) |27| `weak_delegate` | warning | Delegates should be weak to avoid retain cycles |2829## SwiftLint: `async_without_await`3031- **Intent**: A declaration should not be `async` if it never awaits.32- **Never "fix"** by inserting fake suspension (e.g. `await Task.yield()`, `await Task { ... }.value`). Those mask the real issue and add meaningless suspension points.33- **Legit use of `Task.yield()`**: OK in tests or scheduling control when you truly need a yield; not as a lint workaround.3435### Diagnose why the declaration is `async`361) **Protocol requirement** — the protocol method/property is `async`.372) **Override requirement** — base class API is `async`.383) **`@concurrent` requirement** — stays `async` even without `await`.394) **Accidental/legacy `async`** — no caller needs async semantics.4041### Preferred fixes (order)421) **Remove `async`** (and adjust call sites) when no async semantics are needed.432) If `async` is required (protocol/override/@concurrent):44- Re-evaluate the upstream API if you own it (can it be non-async?).45- If you cannot change it, keep `async` and **narrowly suppress the rule** where appropriate (common for mocks/stubs/overrides).4647### Suppression examples (keep scope tight)48```swift49// swiftlint:disable:next async_without_await50func fetch() async { perform() }5152// For a block:53// swiftlint:disable async_without_await54func makeMock() async { perform() }55// swiftlint:enable async_without_await56```5758### Quick checklist59- [ ] Confirm if `async` is truly required (protocol/override/@concurrent).60- [ ] If not required, remove `async` and update callers.61- [ ] If required, prefer localized suppression over dummy awaits.62- [ ] Avoid adding new suspension points without intent.6364## Compiler Warnings: Sendable & Isolation6566The Swift compiler generates concurrency-related warnings based on strict concurrency checking level.6768### Common Warning Patterns6970**"Capture of non-sendable type"**71```swift72// Warning: Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure73Task {74self.doWork() // 'self' is non-Sendable75}76```7778**Fixes (in order of preference):**791. Make the type `Sendable` if it's truly thread-safe802. Use `@MainActor` isolation if it's UI-related813. Capture only Sendable values instead of `self`824. Use `@unchecked Sendable` with documented safety invariant (last resort)8384**"Non-sendable result returned"**85```swift86// Warning: Non-sendable type 'MyResult' returned by implicitly async call87let result = await actor.getData() // Returns non-Sendable type88```8990**Fixes:**911. Make the return type Sendable922. Return Sendable projections (IDs, copies of data)933. Keep processing within the actor's isolation9495### Actor Isolation Warnings9697**"Main actor-isolated property accessed from non-isolated context"**98```swift99// Warning: Main actor-isolated property 'title' cannot be referenced from a non-isolated context100func updateTitle() {101viewModel.title = "New" // viewModel is @MainActor102}103```104105**Fixes:**1061. Mark the calling function `@MainActor`1072. Use `await MainActor.run { }` for one-off access1083. Reconsider if the property truly needs @MainActor isolation109110## Suppression Strategies111112### When to Suppress vs. Fix113114**Fix when:**115- The warning identifies a real data race risk116- The fix is straightforward (add Sendable, adjust isolation)117- The code is new or actively maintained118119**Suppress when:**120- Protocol/inheritance requires the signature121- Third-party code forces the pattern122- Migration is in progress (with tracked ticket)123124### Suppression Annotations125126```swift127// Suppress Sendable warnings for legacy imports128@preconcurrency import LegacyFramework129130// Suppress for a single declaration131nonisolated(unsafe) var legacyCallback: (() -> Void)?132133// Type-level suppression (use sparingly)134struct LegacyWrapper: @unchecked Sendable {135// Document why this is safe136private let lock = NSLock()137private var value: Int138}139```140141### Documentation Requirements142143When using suppression annotations, document:1441. **Why** the suppression is needed1452. **What** invariant makes it safe1463. **When** it can be removed (link to migration ticket)147148```swift149/// Thread-safe: Internal lock protects all mutations.150/// TODO: Remove @unchecked when migrated to actor (JIRA-1234)151final class ThreadSafeCache: @unchecked Sendable {152private let lock = NSLock()153private var storage: [String: Data] = [:]154}155```156