swift-idioms
SkillAI & modelsSwift rewards value types, optionals, and protocol-oriented design. Idiomatic Swift = safe, expressive, Swifty.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the swift-idioms skill
About this capability
Comprehensive sets of standards and practices designed to elevate the capabilities of AI coding agents.
What this skill tells your AI
The instructions your AI receives, as published by irahardianto/awesome-agv in .agents/skills/swift-idioms/SKILL.md and read by ahel’s review.
Swift Idioms and Patterns
Swift rewards value types, optionals, and protocol-oriented design. Idiomatic Swift = safe, expressive, Swifty.
Scope: Swift coding idioms. Test naming: .agents/rules/testing-strategy.md.
Value Types and Optionals
-
Prefer structs over classes — value semantics by default. Classes only for identity, inheritance, or reference counting.
-
Optionals — never force-unwrap (
!) in production:// ✅ Guard let for early exit guard let task = storage.findById(id) else { throw TaskError.notFound(id) } // ✅ Optional chaining let title = task?.title ?? "Untitled" // ✅ if let for conditional binding if let deadline = task.deadline { scheduleReminder(for: deadline) } // ❌ Force unwrap — crash risk let task = storage.findById(id)! -
Property wrappers for reusable behavior:
@propertyWrapper struct Clamped<Value: Comparable> { var wrappedValue: Value { didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } } let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { self.range = range self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } } struct Task { @Clamped(0...100) var progress: Int = 0 }
Error Handling
For universal error handling principles, see
.agents/rules/error-handling-principles.md.
-
Typed throws (Swift 6) or
Errorprotocol:enum TaskError: Error, LocalizedError { case notFound(String) case validationFailed(field: String, message: String) case storageUnavailable var errorDescription: String? { switch self { case .notFound(let id): "Task '\(id)' not found" case .validationFailed(let field, let msg): "Validation failed on \(field): \(msg)" case .storageUnavailable: "Storage is unavailable" } } } func getTask(id: String) throws(TaskError) -> Task { ... } -
Resulttype for async callbacks (pre-async/await):func fetchTask(id: String) async -> Result<Task, TaskError> { ... } -
do/catchwith pattern matching:do { let task = try getTask(id: "123") process(task) } catch TaskError.notFound(let id) { logger.warn("Task not found", metadata: ["taskId": id]) } catch { logger.error("Unexpected error", metadata: ["error": "\(error)"]) } -
deferfor cleanup:func processFile(at path: String) throws -> Data { let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) defer { handle.closeFile() } // ✅ Always runs on exit return handle.readDataToEndOfFile() }
Protocol-Oriented Design
-
Protocols over abstract classes:
// ✅ Interface defined as protocol protocol TaskStorage { func getById(_ id: String) async throws -> Task? func save(_ task: Task) async throws } // ✅ Production implementation struct PostgresTaskStorage: TaskStorage { let pool: ConnectionPool func getById(_ id: String) async throws -> Task? { try await pool.query("SELECT * FROM tasks WHERE id = $1", [id]).first } func save(_ task: Task) async throws { try await pool.execute("INSERT INTO tasks ...", [task.id, task.title]) } } // ✅ Test implementation struct MockTaskStorage: TaskStorage { var tasks: [Task] = [] func getById(_ id: String) async throws -> Task? { tasks.first { $0.id == id } } func save(_ task: Task) async throws { tasks.append(task) } } -
Protocol extensions for default implementations:
protocol Identifiable { var id: String { get } } extension Identifiable { var isNew: Bool { id.isEmpty } } -
Associated types for generic protocols:
protocol Repository { associatedtype Entity func findById(_ id: String) async throws -> Entity? func save(_ entity: Entity) async throws }
Concurrency
-
Structured concurrency with
async/await:func loadDashboard() async throws -> Dashboard { async let user = fetchUser(id) async let tasks = fetchTasks(userId: id) async let stats = fetchStats() return Dashboard( user: try await user, tasks: try await tasks, stats: try await stats ) } -
@Sendablefor closures crossing concurrency domains. -
Actors for thread-safe mutable state:
actor TaskCache { private var cache: [String: Task] = [:] func get(_ id: String) -> Task? { cache[id] } func set(_ id: String, task: Task) { cache[id] = task } func invalidate(_ id: String) { cache.removeValue(forKey: id) } } // ✅ Safe concurrent access let cache = TaskCache() await cache.set("123", task: newTask) if let task = await cache.get("123") { ... } -
TaskGroupfor dynamic concurrency:func fetchAllTasks(ids: [String]) async throws -> [Task] { try await withThrowingTaskGroup(of: Task.self) { group in for id in ids { group.addTask { try await fetchTask(id: id) } } return try await group.reduce(into: []) { $0.append($1) } } }
Naming (Swift API Design Guidelines)
- camelCase for functions, properties, variables.
- PascalCase for types, protocols, enums.
- Omit needless words —
remove(at:)notremoveItem(atIndex:). - Protocols for capabilities use
-able/-ible:Codable,Identifiable. - Factory methods use
makeprefix:makeIterator(). - Boolean properties read as assertions:
isEmpty,hasChanges,isValid.
Anti-Patterns
- ❌ Force unwrap (
!) in production code — crashes at runtime - ❌
varwhenletsuffices — always prefer immutability - ❌ Classes when structs work — unnecessary reference semantics
- ❌ Stringly-typed APIs — use enums for finite option sets
- ❌ Massive view controllers — extract to view models, coordinators
- ❌
try?silently discarding errors — log or handle the error case - ❌ Nested
if letpyramids — useguard letfor early returns
// ❌ Pyramid of doom
if let user = getUser() {
if let tasks = getTasks(for: user) {
if let first = tasks.first {
process(first)
}
}
}
// ✅ Flat with guard
guard let user = getUser() else { return }
guard let tasks = getTasks(for: user), let first = tasks.first else { return }
process(first)
Testing
XCTest or Swift Testing (6.0+). Mock via protocols.
// XCTest
final class TaskServiceTests: XCTestCase {
func testGetTask_returnsNotFound() async throws {
let storage = MockTaskStorage()
let service = TaskService(storage: storage)
do {
_ = try await service.getTask(id: "999")
XCTFail("Expected notFound error")
} catch TaskError.notFound(let id) {
XCTAssertEqual(id, "999")
}
}
}
// Swift Testing (6.0+)
@Test func getTask_returnsNotFound() async throws {
let storage = MockTaskStorage()
let service = TaskService(storage: storage)
#expect(throws: TaskError.notFound("999")) {
try await service.getTask(id: "999")
}
}
Formatting and Static Analysis
| Tool | Purpose | Command |
|---|---|---|
swift-format | Formatting | swift-format -i -r Sources/ |
| SwiftLint | Linting | swiftlint lint --strict |
| Xcode Analyzer | Static analysis | Built-in |
Related
- Code Idioms and Conventions .agents/rules/code-idioms-and-conventions.md
- Testing Strategy .agents/rules/testing-strategy.md
- Error Handling Principles .agents/rules/error-handling-principles.md
- Concurrency and Threading Principles @.agents/rules/concurrency-and-threading-principles.md
Signals
- GitHub stars
- 156
- Forks
- 53
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
swift-idioms- Source
- github.com/irahardianto/awesome-agv