.claude/skills/swift-concurrency/SKILL.md
Swift concurrency patterns for this project — async/await, Sendable, thread safety, and MainActor usage
npx skillsauth add mohitmujawdiya/dagabaaz swift-concurrencyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@MainActor on the class level for AppDelegate and any class that primarily interacts with UI. This means all methods and properties are MainActor-isolated by default — no need for per-method annotations.
Never nest @MainActor inside @MainActor. If a class is @MainActor, its methods don't need the annotation again. Adding it is noise.
Callbacks from non-MainActor contexts:
// CORRECT — dispatches to MainActor from any thread
client.onEvent = { [weak self] value in
Task { @MainActor in self?.viewModel.update(value) }
}
// WRONG — mixing DispatchQueue with MainActor
client.onEvent = { [weak self] value in
DispatchQueue.main.async { self?.viewModel.update(value) }
}
@unchecked Sendable requires a documented locking strategy. Every mutable property must be protected by a named lock.
final class AudioCapture: @unchecked Sendable {
private let lock = NSLock()
private var _onAudioChunk: ((Data) -> Void)? // Protected by lock
var onAudioChunk: ((Data) -> Void)? {
get { lock.withLock { _onAudioChunk } }
set { lock.withLock { _onAudioChunk = newValue } }
}
}
Read-then-act must be atomic. Don't read a locked value and then use it outside the lock if another thread could change it. Capture the values you need in a single lock acquisition:
// CORRECT — single lock acquisition
let callbacks = lock.withLock { (_onAudioChunk, _onAudioLevel) }
callbacks.0?(data)
callbacks.1?(level)
// WRONG — two separate lock acquisitions, state could change between them
let chunk = lock.withLock { _onAudioChunk }
let level = lock.withLock { _onAudioLevel }
Callback closures on @unchecked Sendable types should be marked @Sendable where they'll be called from background threads. This enforces that captured values are Sendable.
Background tasks from methods should use [weak self] to prevent retain cycles:
Task { [weak self] in await self?.receiveLoop() }
Don't fire-and-forget important Tasks. If a Task's failure matters, capture it and handle errors. For WebSocket receive loops, the loop itself handles errors internally.
Teardown before setup. When startSession is called, first clean up any existing session (stop audio, disconnect WebSocket) before creating new ones. This prevents resource leaks on reconnection.
URLSession must be invalidated on cleanup. Unlike most objects, URLSession retains its delegate and won't deallocate without invalidateAndCancel().
development
WebSocket and Gemini Live API patterns — connection management, audio streaming, and response handling
development
Swift macOS code quality standards — architecture, patterns, and anti-patterns for native macOS apps
tools
Notch overlay UI patterns — NSPanel setup, SwiftUI overlay views, screen-sharing hiding, and display adaptation
data-ai
Example TaskFlow authoring pattern for inbox triage. Use when messages need different treatment based on intent, with some routes notifying immediately, some waiting on outside answers, and others rolling into a later summary.