swift-coding
Ensures Swift code quality and safety by enforcing best practices for error handling, concurrency, and memory management.
Install
mkdir -p .claude/skills/swift-coding && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18678" && unzip -o skill.zip -d .claude/skills/swift-coding && rm skill.zipInstalls to .claude/skills/swift-coding
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Apply when writing or editing Swift (.swift) files. Behavioral corrections for error handling, concurrency, memory management, type safety, protocol-oriented design, security defaults, and common antipatterns. Project conventions always override these defaults.Key capabilities
- →Correct error handling in Swift files
- →Improve concurrency patterns
- →Manage memory with weak/unowned references
- →Ensure type safety with generics and protocols
- →Apply protocol-oriented design principles
- →Prevent common Swift antipatterns
How it works
The skill applies behavioral corrections based on Swift best practices and 'never' rules, prioritizing project conventions.
Inputs & outputs
When to use swift-coding
- →Optimizing concurrency in Swift apps
- →Refactoring legacy Swift code for safety
- →Fixing memory management issues with self capture
- →Implementing robust error handling patterns
About this skill
Swift Coding
Match the project's existing conventions. When uncertain, read 2-3 existing files to infer the local style. Check Package.swift or .xcodeproj/.xcworkspace for Swift version, platform targets, and dependencies. New Xcode 26+ app projects default to Approachable Concurrency (Swift 6.2+) with main-actor isolation for app module code — assume this default unless the project explicitly disables it. These defaults apply only when the project has no established convention.
Never rules
These are unconditional. They prevent bugs and vulnerabilities regardless of project style.
- Never force unwrap (
!) outside tests and controlled contexts — crashes at runtime with no recovery path. Useguard let,if let,??, or optional chaining. Force unwrap is acceptable in tests,@IBOutlet, and when failure is provably a programmer error with an explaining comment. - Never
try!outside tests — crashes on any error. Usedo-catch,try?, or propagate withthrows. A network timeout, a missing file, a malformed response — any of these kill your process silently with no chance to recover or report. - Never bare
catch { }without handling — silently swallows errors. Catch specific error types, or log and rethrow. A decode failure looks the same as a permission error, and you'll never know which one is happening in production. - Never
[weak self]without checking for nil — accessing self after capture withoutguard let selfleads to silent no-ops or partial execution. Half-completed operations are worse than failures because they corrupt state without raising errors. - Never mutable global or static state — shared mutable state causes data races. Use actors,
@MainActor, or dependency injection. The Swift 6 concurrency model will flag these as compile errors, so fix them now. - Never blocking calls on MainActor — no
Thread.sleep(), synchronous network calls, or heavy computation on the main thread. UseTask,async/await, or dispatch to a background context. Blocking main freezes the UI and triggers watchdog kills on iOS. - Never
x = x + yto build strings in loops — use+=orappend(contentsOf:), which grow the buffer in place (amortized, via copy-on-write), orjoined(separator:)for collections.x = x + yallocates and copies a fresh string every iteration. - Never hand-roll cryptographic key material — use CryptoKit (
SymmetricKey(size:)) orSecRandomCopyBytesfor keys, tokens, and session IDs. For general randomness,Int.random(in:)andSystemRandomNumberGeneratorare fine — the system RNG is cryptographically secure. - Never
print()in production code — useos.LoggerorOSLog.printis not filterable, not structured, and persists in release builds. It cannot be searched in Console.app and adds noise to device logs. - Never
classwhenstructsuffices — default to value types. Useclassonly when you need reference semantics, inheritance, or identity. Value types avoid retain/release overhead and are safe to copy across threads — though closure captures, existential boxes, and CoW buffers can still put struct storage on the heap. - Never retain cycles in closures — escaping closures capturing
selfin classes must use[weak self]or[unowned self]. Retain cycles cause silent memory leaks that accumulate over app lifetime, eventually triggering OOM kills. - Never
AnyorAnyObjectwhen a protocol or generic suffices — type erasure disables compile-time checking. Use generics,some, oranywith specific protocols. A runtime cast failure is always worse than a compile error.
Error handling
Use throws and do-catch at system boundaries. Propagate errors with throws through internal layers and handle them at the outermost boundary where you can take meaningful action.
enum NetworkError: Error {
case invalidURL(String)
case requestFailed(statusCode: Int)
case decodingFailed(underlying: Error)
}
func fetchUser(id: Int) async throws -> User {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
throw NetworkError.invalidURL("/users/\(id)")
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
throw NetworkError.requestFailed(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0)
}
do {
return try JSONDecoder().decode(User.self, from: data)
} catch {
throw NetworkError.decodingFailed(underlying: error)
}
}
Use guard for early exits. It keeps the happy path unindented and makes preconditions explicit:
func process(order: Order?) throws -> Receipt {
guard let order else { throw AppError.missingOrder }
guard order.items.isEmpty == false else { throw AppError.emptyOrder }
guard order.total > 0 else { throw AppError.invalidTotal(order.total) }
return Receipt(order: order, timestamp: .now)
}
When to use Result vs throws: prefer throws for most code. Use Result when you need to store an outcome for later processing, pass it across non-throwing boundaries, or work with callback-based APIs that cannot be made async.
Swift 6 typed throws allow callers to handle specific error types without type erasure:
func validate(input: String) throws(ValidationError) -> Validated {
guard input.count >= 3 else { throw .tooShort(minimum: 3) }
return Validated(value: input)
}
Resource cleanup
Use defer for cleanup — file handles, locks, temporary state restoration. defer executes when the scope exits regardless of how (return, throw, break):
func writeData(_ data: Data, to path: String) throws {
let handle = try FileHandle(forWritingTo: URL(filePath: path))
defer { try? handle.close() }
handle.write(data)
}
With structured concurrency, child tasks are automatically cancelled when their parent scope exits. Prefer structured concurrency (async let, TaskGroup) over unstructured Task { } to get automatic cleanup for free.
Async patterns
Use async/await for all asynchronous work. Prefer structured concurrency over unstructured tasks.
TaskGroup for concurrent work with dynamic fan-out:
func fetchAllUsers(ids: [Int]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask { try await fetchUser(id: id) }
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
Actors for shared mutable state — they serialize access automatically:
actor ImageCache {
private var cache: [URL: Data] = [:]
func image(for url: URL) -> Data? { cache[url] }
func store(_ data: Data, for url: URL) { cache[url] = data }
}
Use @MainActor for UI work. Apply it to the type when all members need main-thread access, or to individual methods when only some do:
@MainActor
@Observable
final class ViewModel {
var items: [Item] = []
func refresh() async throws {
let fetched = try await service.fetchItems()
items = fetched
}
}
Sendable conformance is required for values crossing actor boundaries. Structs and enums with all-Sendable stored properties conform automatically. For classes, use a final class with only let stored properties, declared Sendable — or @unchecked Sendable with internal synchronization.
sending parameters and returns (Swift 6.0+) let you pass a non-Sendable value across isolation boundaries exactly once. Use when you need ownership transfer without requiring Sendable:
func enqueue(_ work: sending () async -> Void) async { /* ... */ }
actor Pipeline {
func submit(_ task: sending SomeNonSendable) async { /* ownership moves here */ }
}
Check cancellation in long-running work:
for item in largeCollection {
try Task.checkCancellation()
await process(item)
}
When to use structured vs unstructured concurrency: use async let and TaskGroup (structured) for work scoped to a function. Use Task { } (unstructured) only for fire-and-forget work or bridging from synchronous contexts. Structured concurrency handles cancellation and error propagation automatically.
Type system
some (opaque) returns one concrete type hidden from the caller — preserves type identity and compiler optimization. any (existential) is a type-erased box — reserve it for heterogeneous collections or genuinely dynamic dispatch; it costs boxing and blocks specialization.
Value vs reference types
Default to structs. Use classes when you need reference semantics (shared mutable state), inheritance, or Objective-C interop. Use enum as a namespace (caseless enum) for grouping constants and static functions:
enum API {
// Force unwrap is safe: hard-coded literal is a valid URL, so failure is a programmer error
static let baseURL = URL(string: "https://api.example.com")!
static let timeout: TimeInterval = 30
}
Collections use copy-on-write — passing an array to a function does not copy until mutation. This means value semantics are cheap in practice for standard library types.
Data modeling
Choose by semantics: Codable structs for API payloads (declare them Sendable — they cross concurrency boundaries in async networking), enums with associated values for fixed sets of states, @Observable classes for SwiftUI UI state, and class or actor only where identity or shared mutable state is the point.
Observation (SwiftUI)
@Observable (Swift 5.9+) replaces the ObservableObject + @Published + Combine pairing. SwiftUI views observe only the properties they actually read — automatic, granular, no manual @Published.
@Observable
final class Cart {
var items: [I
---
*Content truncated.*
When not to use it
- →When editing files other than Swift (.swift) files
- →When project conventions explicitly override these defaults
- →When the goal is to introduce known antipatterns
Limitations
- →Focuses on behavioral corrections for specific Swift language features
- →Does not infer project-specific requirements beyond explicit conventions
How it compares
This skill provides specific, actionable corrections for common Swift issues, unlike general coding advice.
Compared to similar skills
swift-coding side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| swift-coding (this skill) | 0 | 18d | Review | Intermediate |
| feature-toggle-developer | 1 | 6mo | Review | Intermediate |
| swiftui-view-refactor | 0 | 6mo | No flags | Intermediate |
| swiftdata-pro | 0 | 3mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by Dynokostya
View all by Dynokostya →You might also like
feature-toggle-developer
anyproto
Guides systematic removal of feature toggles from the codebase with automated cleanup detection. Use when removing feature flags, enabling toggles permanently, or cleaning up unused code after toggle removal.
swiftui-view-refactor
hot666666
Refactor and review SwiftUI view files for consistent structure, dependency injection, and Observation usage. Use when asked to clean up a SwiftUI view’s layout/ordering, handle view models safely (non-optional when possible), or standardize how dependencies and @Observable state are initialized and
swiftdata-pro
sobchak-security
Writes, reviews, and improves SwiftData code using modern APIs and best practices. Use when reading, writing, or reviewing projects that use SwiftData.
skill-code-review
nicanac
Perform thorough, constructive code reviews focusing on correctness, security, maintainability, and best practices
swift-concurrency
AvdLee
Expert guidance on Swift Concurrency best practices, patterns, and implementation. Use when developers mention: (1) Swift Concurrency, async/await, actors, or tasks, (2) "use Swift Concurrency" or "modern concurrency patterns", (3) migrating to Swift 6, (4) data races or thread safety issues, (5) refactoring closures to async/await, (6) @MainActor, Sendable, or actor isolation, (7) concurrent code architecture or performance optimization, (8) concurrency-related linter warnings (SwiftLint or similar; e.g. async_without_await, Sendable/actor isolation/MainActor lint).
swift-concurrency-expert
Dimillian
Swift Concurrency review and remediation for Swift 6.2+. Use when asked to review Swift Concurrency usage, improve concurrency compliance, or fix Swift concurrency compiler errors in a feature or file.