This skill should be used when the user asks to "write Swift code", "create a Swift type", "set up a Swift package", "review Swift code", "refactor Swift", "use async/await in Swift", "fix Swift style", or when generating any Swift source code regardless of target platform. Provides modern Swift 6+ best practices covering type system, optionals, concurrency, error handling, protocols, generics, and idiomatic patterns. Does not cover any specific platform or framework.
71
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Modern Swift best practices for writing safe, expressive, and idiomatic code. Targets Swift 6+ — language and standard library only, no platform frameworks.
Applicable to all Swift targets: Apple platforms, server-side (Vapor, Hummingbird), CLI tools, cross-platform (Linux, Windows).
references/type-system.md — Generics, protocols with associated types, opaque types (some), existentials (any), metatypes, @dynamicMemberLookup, @dynamicCallablereferences/concurrency.md — Actors, task groups, async sequences, Sendable, isolation, GCD migration, Combine interopreferences/patterns.md — Property wrappers, result builders, key paths, Codable, extensions, copy-on-write, DSL designreferences/project-structure.md — Swift Package Manager setup, multi-target packages, build configurations, plugins, testing setupreferences/static-analysis.md — SwiftLint (configuration, rules, custom rules, auto-correct), SwiftFormat (configuration, formatting rules), combined setup, CI/CD integration, pre-commit hooks@_exported unless building a module facade.private or internal. Use public only at module boundaries.| Element | Convention | Example |
|---|---|---|
| Type / Protocol | PascalCase | UserProfile, Fetchable |
| Function / Property | camelCase | fetchUser(), isActive |
Constant (let at module scope) | camelCase | defaultTimeout |
| Enum case | camelCase | case loading, case success(Data) |
| Boolean | Prefix is, has, can, should | isValid, hasPermission |
| File name | PascalCase.swift | UserProfile.swift |
| Protocol | Adjective or -able/-ible suffix | Loadable, Configurable |
| Generic parameter | Descriptive or single uppercase | Element, T, Key, Value |
Follow the Swift API Design Guidelines:
distance(to:)), verb for mutating (sort()), -ed/-ing for non-mutating variant (sorted(), removing()).struct User { // Value type — prefer by default
let id: UUID
var name: String
}
class UserStore { // Reference type — when identity matters
private var users: [User] = []
}Use struct by default. Use class when you need identity, inheritance, or reference semantics. Actors when you need thread-safe mutable state.
let name: String? = user?.name // safe chaining
let displayName = name ?? "Anonymous" // nil coalescing
// NEVER force-unwrap unless you have a provable invariant with a comment
guard let user = fetchUser(id) else { return } // early exit — preferred// Shorthand optional binding (Swift 5.7+) — use when binding name matches the variable
if let name {
print(name)
}
guard let user else { return }
// Explicit binding — only when intentionally binding to a different name
if let email = user.email {
sendConfirmation(to: email)
}
// map / flatMap
let uppercased = name.map { $0.uppercased() } // String?
let nested = fetchUser(id).flatMap { $0.address } // Address?
// Coalescing with throwing
let user = try optionalUser ?? { throw AppError.notFound }()Always use shorthand optional binding when the binding name matches the original variable name. Write if let value { } instead of if let value = value { }. This applies to all optional binding contexts: if let, guard let, while let, and multi-condition bindings (e.g., guard let foo, let bar else { }). Only use the explicit if let renamed = original form when intentionally binding to a different name.
NEVER use ! to silence the compiler. Acceptable only with a provable invariant and a comment explaining why.
enum LoadingState<T> {
case idle
case loading
case loaded(T)
case failed(Error)
}Prefer enums over boolean flags for state modeling. Enables exhaustive switch.
protocol Repository {
associatedtype Entity
func fetch(id: UUID) async throws -> Entity
func save(_ entity: Entity) async throws
}
extension Array where Element: Identifiable {
func element(withID id: Element.ID) -> Element? {
first { $0.id == id }
}
}Rules:
Fetchable & Cacheable) over large monolithic protocols.For generics, opaque types, existentials, and advanced protocol patterns see references/type-system.md.
// Define domain errors
enum AppError: LocalizedError {
case networkUnavailable
case unauthorized
case notFound(resource: String)
var errorDescription: String? {
switch self {
case .networkUnavailable: "Network is unavailable"
case .unauthorized: "Authentication required"
case .notFound(let resource): "\(resource) not found"
}
}
}
// Use typed throws (Swift 6+)
func fetchUser(id: UUID) async throws(AppError) -> User { ... }
// Use Result for expected outcomes in callbacks
func validate(_ input: String) -> Result<ValidatedInput, ValidationError> { ... }Rules:
guard for preconditions with early exit.Error broadly unless at a top-level boundary.async throws over Result for async operations.LocalizedError for user-facing error messages.// async/await
func fetchUser(id: UUID) async throws -> User {
let data = try await networkClient.get("users/\(id)")
return try decode(data)
}
// Structured concurrency — parallel execution
func loadDashboard() async throws -> Dashboard {
async let profile = fetchProfile()
async let notifications = fetchNotifications()
return Dashboard(profile: try await profile, notifications: try await notifications)
}
// Actor — thread-safe mutable state
actor Cache<Key: Hashable, Value> {
private var storage: [Key: Value] = [:]
func get(_ key: Key) -> Value? { storage[key] }
func set(_ key: Key, value: Value) { storage[key] = value }
}Rules:
async let, TaskGroup. Avoid Task.detached unless truly needed.Task or actors.Sendable.Task.isCancelled or try Task.checkCancellation() to respond to cancellation.nonisolated explicitly when actor methods don't need isolation.For actors, task groups, async sequences, Sendable patterns, GCD migration, and Combine interop see references/concurrency.md.
// Prefer higher-order functions over manual loops
let activeUsers = users.filter { $0.isActive }
let names = users.map(\.name)
let totalAge = users.reduce(0) { $0 + $1.age }
// Lazy for large collections
let firstMatch = users.lazy.filter { $0.isActive }.first
// Dictionary grouping
let byDepartment = Dictionary(grouping: employees, by: \.department)// Trailing closure syntax
let sorted = users.sorted { $0.name < $1.name }
// Multi-line closures — use named parameters
let transformed = items.map { item in
Item(
id: item.id,
name: item.name.uppercased()
)
}
// @escaping — when closure outlives the function
func fetch(completion: @escaping (Result<User, Error>) -> Void) { ... }
// @Sendable — when closure crosses concurrency boundaries
func execute(_ work: @Sendable () async -> Void) { ... }Rules:
$0, $1 only for short, single-expression closures.async functions over completion handler closures in new code.MyPackage/
├── Package.swift
├── Sources/
│ ├── MyLibrary/
│ │ ├── Models/
│ │ ├── Services/
│ │ └── Extensions/
│ └── MyExecutable/
│ └── main.swift
├── Tests/
│ └── MyLibraryTests/
└── Plugins/Rules:
For SPM setup, multi-target packages, build configurations, plugins, and testing see references/project-structure.md.
test_methodName_whenCondition_expectedResult.protocol UserRepository {
func fetch(id: UUID) async throws -> User
}
// Production
struct RemoteUserRepository: UserRepository {
let client: HTTPClient
func fetch(id: UUID) async throws -> User { ... }
}
// Test
struct FakeUserRepository: UserRepository {
var stubbedUser: User?
func fetch(id: UUID) async throws -> User {
guard let user = stubbedUser else { throw AppError.notFound(resource: "User") }
return user
}
}import Testing
@Test("Fetches user by ID")
func fetchUser() async throws {
let repo = FakeUserRepository(stubbedUser: User(id: testID, name: "Alice"))
let user = try await repo.fetch(id: testID)
#expect(user.name == "Alice")
}
@Test("Throws when user not found")
func fetchUserNotFound() async {
let repo = FakeUserRepository(stubbedUser: nil)
await #expect(throws: AppError.self) {
try await repo.fetch(id: testID)
}
}Prefer Swift Testing (@Test, #expect) over XCTest for new code. Use XCTest when the project requires it or for UI/integration tests on Apple platforms.
| Mistake | Fix |
|---|---|
Force-unwrapping (!) without invariant | Use guard let, if let, ??, or optional chaining |
if let value = value { } when names match | Use shorthand: if let value { } (Swift 5.7+) |
Catching Error broadly | Catch specific error types at appropriate boundaries |
Task { } without cancellation handling | Check Task.isCancelled or use Task.checkCancellation() |
Using GCD (DispatchQueue) in new code | Use Swift Concurrency (async/await, actors) |
Mutable var in struct properties without need | Default to let; use var only when mutation is required |
| Large protocols (>5 requirements) | Break into focused protocols; use composition |
Ignoring Sendable warnings | Conform value types or use actor isolation |
| Completion handlers in new code | Use async/await; wrap legacy APIs with withCheckedContinuation |
| Stringly-typed APIs | Use enums, phantom types, or value types for type safety |
Any / AnyObject without reason | Use generics or existentials with constraints |
Double for money | Use Decimal with explicit rounding |
| Hard-coded dependencies | Protocol-based DI via init injection |
#if directives for multi-target polymorphism | Use DI or separate file implementations per target; reserve #if for DEBUG, os(), canImport() |
298fce7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.